How to rewrite a simple doLast task to be cacheable?

Hello.

I inspected this project with the build scan plugin to find out if there are tasks which are not cached. I found three of them. Here is one example:

def torBinariesDir = 'src/main/res/raw'

task cleanTorBinaries {
	doLast {
		delete fileTree(torBinariesDir) { include '*.zip' }
	}
}

clean.dependsOn cleanTorBinaries

What would be the recommended way to make the task cacheable?

A cacheable task stores the output files of a task in order to avoid future processing work. Since your example task has no output files it makes no sense to make it a cacheable task (there’s nothing to cache from a delete operation).

Perhaps one of your other tasks is a better example to work from?

It could be rewritten as

task cleanTorBinaries {
   ext.deleteMe = fileTree(torBinariesDir) { include '*.zip'} 
   inputs.files deleteMe 
   outputs.files deleteMe
   doLast {
      delete deleteMe
   } 
}

Or simply

task cleanTorBinaries(type: Delete) 
    targetFiles = fileTree(torBinariesDir) { include '*.zip'} 
} 

@Lance I get the following failure when I use your simplified version:

Cannot set the value of read-only property ‘targetFiles’ for task ‘cleanTorBinaries’ of type org.gradle.api.tasks.Delete.

Ah, just do

task cleanTorBinaries(type: Delete) 
    delete fileTree(torBinariesDir) { include '*.zip'} 
} 

Note that in this instance Delete.delete(...) is overriding/hiding Project.delete(...)

(confusing, I know. see Closure.DELEGATE_FIRST)

@Lance There is no failure with this version of the task. But the build scan states that the task is not cacheable:

The task was not up-to-date because it did not have any declared outputs

@johnjohndoe You cannot make a delete task cacheable and that is perfectly fine. Is this from the suggestions tab?

@Stefan_Wolf I took a look at the “Not cacheable” tasks at “Performance / Task execution”.

Yeah, come to think of it a delete task should never be cacheable. Typically a delete task is used to clean rather than build.

Is this delete task part of your build? Or just part of your clean?

If you’re deleting as part of your build, perhaps you could change the logic so that it’s not required. I’d need to understand your build better. Perhaps you could instead copy zips from torBinariesDir to another location and use that instead. Or if you previously unzipped to torBinariesDir you could limit your unzipping to *.zip

In a perfect world you could run your build twice in a row and the second run would do nothing since everything’s cached. With a Delete task in your build this will never be true

Here is the source code lines of the task in the project:

So, the delete is happening as part of your “clean” which is not related to your “build”

It’s perfectly fine for this to not be cacheable

I agree with regards to the clean task.
Further down in the script I see:

tasks.withType(MergeResources) {
	inputs.dir torBinariesDir
	dependsOn unpackTorBinaries
}

Not sure, when MergeResources is executed - it might be more often and therefore trigger the cleanTorBinaries task transitively.

I see this which isn’t cacheable

task unpackTorBinaries {
	doLast {
		copy {
			from configurations.tor.collect { zipTree(it) }
			into torBinariesDir
			// TODO: Remove after next Tor upgrade, which won't include non-PIE binaries
			include 'geoip.zip', '*_pie.zip'
		}
	}
	dependsOn cleanTorBinaries
}

You could could change to this which is cacheable

task unpackTorBinaries(type: Copy) {
   from configurations.tor.collect { zipTree(it) }
   into torBinariesDir
   include 'geoip.zip', '*_pie.zip'   
   doFirst {
      delete torBinariesDir
   } 
} 

Or perhaps if unzipping is expensive and you want to delay until execution phase

task unpackTorBinaries {
   inputs.files configurations.tor
   outputs.dir torBinariesDir
   doLast {
      delete torBinariesDir
      copy {
         from configurations.tor.collect { zipTree(it) }
         into torBinariesDir
         include 'geoip.zip', '*_pie.zip'   
      }      
   } 
} 

See Feature Request: Unzip Task