Gradle 7.0 seems to take an overzealous approach to inter-task dependencies

Actually there are mutlipe things to mention.

Having tasks with overlapping outputs is bad, as this works against up-to-date checks and cacheability and as you have seen also makes problems with other optimizations. Each task shoud have a dedicated output that he owns exclusively. In case of a Copy task, the output is the outputDirectory property and so any files in there at the end of the task execution are considered an output, even if they originate from some other task that was accidentally or incidentally run before. That’s also why for example task output caching for cacheable tasks is disabled automatically if overlapping outputs are found.

Due to this you can for example also not use a task of type Copy to copy something into the project directory like for example an updated readme file from a template, as then the whole directory, including VCS metadata, build artifacts and everything would count as output of the task and that would not only be incorrect but even fail to calculate the hashes. In such a case you could for example instead make an ad-hoc task where you define the actual output files using outputs.file and then use project.copy { ... } in a doLast { ... } to do the actual copying. The spec for that copy is the same as for the task of type Copy, but as it is not a task of type Copy anymore that defines the destination directory as output directory, but you specify the outputs of the task manually as specific files, problems like the whole project directory being an output or overlapping outputs are gone.

The other thing to mention is, that hard-coding paths and using manual task dependencies is bad practice.
Instead your tasks should properly define their outputs. The tasks using these outputs can then simply refer to the outputs of the task and the task can also be defined as input. If the tasks using these outputs define file collections as inputs or the new lazy properties, you can also simply configure the producing task as the file collection to use, or wire the output property to the input property of the consuming task and you get implicit task dependencies. This then also works if files are generated to other paths or if you don’t need some file anymore and thus also not the task dependency and so on.

With your example project, a more idiomatic version that also fixes the deprecation warning while preserving paths would for example be this:

task doall(dependsOn: ["copyA", "pointless_gzip"])

task copyA {
  outputs.files files("src/main/data/")
          .asFileTree
          .matching { include "*.xsd" }
          .files
          .collect { "${buildDir}/${it.name}" }

  doLast {
    copy {
      from 'src/main/data/'
      into buildDir
      include '*.xsd'
    }
  }
}

task pointless_tar(type: Exec) {
  def outputFile = "${buildDir}/pointless.tar"
  outputs.file outputFile
  commandLine 'tar', '-cf', outputFile, 'src'
}

task pointless_gzip(type: Exec) {
  inputs.files pointless_tar
  outputs.file "${buildDir}/pointless.tar.gz"
  commandLine 'gzip', '-k', files(pointless_tar).singleFile
}
3 Likes