I have started getting TaskInternal.execute() deprecation message and I’d like to move away from it, but I’m having trouble with rewriting my current task setup.
I always need to clean files before generation, but I don’t want to clean them in case they are up to date. I also want to have a way to force file generation.
Could someone please point me to the right direction as to how to rewrite this without execute()?
Think about what each task needs to do as a separate, incremental unit of work. You never need to cleanFiles if your only intent is to generateFiles. Rather, if you generateFiles, you need to delete anything that already exists in the generatedDir.
Rather than referring to another task (outside of the public task API), update your doFirst { } task action to do the actual work that you intend to do. i.e:
There’s a lot that goes into file deletion. You can pass many files/folders to delete, follow symlinks if desired, and there’s logic to try to recover from failed deletes with nuances depending on the system platform. The operation seems trivial because the API is simple and hides most of the complexity from the client code / build script.
Behind the scenes, both the Delete task and the project.delete() method delegate much of their work to the same classes, such as the Deleter that does the traversal and deletion. One just provides an API appropriate for a task while the other provides immediate action, more suited for a task action.
Good object oriented design such as single responsibility still applies here. The one job of your tasks / actions should be to convert what makes sense in their context to what is understood by a collaborator that can actually do the work. You may have multiple clients to your code that does the heavy lifting, but this shouldn’t be a bad thing.
When a task executes, it executes one or more actions. The Delete task has an Action that takes the task configuration and does the actual execution with it. The project.delete() method gives you a public API to access the same behavior (just without abusing the Task implementation details).
There shouldn’t really be duplicated code. Multiple clients with different context calling the same API each have a valid use case that should justify their existence.
You should be able to follow these same principles if you’re implementing custom functionality from scratch.
There are many different levels at which you can expand the Gradle functionality available to your build. Some examples:
functions and classes declared in the build file
adding utility classes to the buildSrc project
adding external utility jars to the buildscript’s classpath dependencies
writing plugins directly in the build file, in buildSrc, or bringing them from external repositories
At the basic end of the spectrum, using your example of a “custom/complicated delete”, one solution could be to declare a function and then call it from both tasks.
void myDelete( File dir ) {
// do special stuff, could include project.delete(...) if needed
}
task cleanFiles { // NOTE: not a Delete task anymore
File dir = generatedDir
doLast {
myDelete( dir )
}
}
task generateFiles( type: Exec ) {
File destDir = generatedDir
...
doFirst {
myDelete( destDir )
}
}