I am currently building a little helper task in my build.gradle to gather some test data, zip it and publish it. That includes several different tasks for copying, metadata creation, zipping and publishing.
What is the current way to create outputs (I want to pass directory names) to another task?
For example I have something like this:
tasks.register('copyTestData', Copy) {
dependsOn 'createTestData'
from createTestData.ext.output() // that's how I currently do it
def outputDir = layout.buildDirectory.dir('testdata')
into outputDir.asFile
ext.output = {
return outputDir.absolutePath
}
}
tasks.register('zipTestData', Zip) {
dependsOn 'createTestData'
from createTestData.ext.output()
archiveFileName = "testdata.zip"
destinationDirectory = layout.buildDirectory.dir("dist")
}
Is there any official way? I mean, this works, but I feels kind of like a workaround.
Yes, using ext is practically always a work-around and you should feel dirty every time you use it.
Also practically any explicit dependsOn without a lifecycle task on the left-hand side is a code smell and usually a sign that you do not write task outputs to task inputs properly, which would come with necessary task dependencies automatically.
And also get()ing a Provider at configuration time if almost always a bad idea. The main earning you get from that are race conditions like when using afterEvaluate, as you don’t know whether the provider is changed further before execution phase.
You also usually never want Copy, but Sync to also get rid of stale files.
So from the top of my head (i.e. untested because on mobile) it should be something like this, given that the createTestData task is properly declaring its outputs:
tasks.register('copyTestData', Sync) {
from createTestData
into layout.buildDirectory.dir('testdata')
}
tasks.register('zipTestData', Zip) {
from createTestData
archiveFileName = 'testdata.zip'
destinationDirectory = layout.buildDirectory.dir("dist")
}
or
def testDataCopySpec = copySpec {
from createTestData
}
tasks.register('copyTestData', Sync) {
with testDataCopySpec
into layout.buildDirectory.dir('testdata')
}
tasks.register('zipTestData', Zip) {
with testDataCopySpec
archiveFileName = 'testdata.zip'
destinationDirectory = layout.buildDirectory.dir("dist")
}
And finally one personal recommendation. I strongly recommend switching to Kotlin DSL. By now it is the default DSL, you immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio.
Thank you for your answer, that helps a lot. Now I’ll get into the shower and after that I’ll clean up my mess.
I didn’t know about Sync, but I guess in my case it wouldn’t make any difference, the test files change every 5-10 years or so.
I used the get() in my experimentation, but of course you’re right this should be cleaned up.
So Sync (and Copy as well) define some kind of output that can just be used by another Task with “from”, that is new to me.
The reason we’re still using Groovy is due to conformity. Every Gradle script in my company is written in Groovy. Changing this will confuse some people so we just stick with Groovy for now. I hope to change it at some time in the future.
So pre-defined tasks can work great in combination, as it seems. I also go another task that I just register to extract the metadata of the files. It’s reading the data and writing it into a csv file. I guess it would be a good idea to create a task class with @OutputFile? I guess that is something that other tasks are able to work with?
So Sync (and Copy as well) define some kind of output that can just be used by another Task with “from”, that is new to me.
Every task should define its inputs and outputs, or otherwise it could never be up-to-date, let alone cachable. And you would always wire task outputs to task inputs and never configure input paths explicitly except for at the very beginning of the chain.
I hope to change it at some time in the future.
Poor you.
But you will not be able to do a big bang change of everything anyway, so just do it incrementally one project after the other.
So pre-defined tasks can work great in combination, as it seems.
All tasks can with great in combination, as long as they are following some good practices like properly declaring its inputs and outputs, and using Property and friends.
I guess it would be a good idea to create a task class with @OutputFile? I guess that is something that other tasks are able to work with?
It is most often a good idea to extract things into proper separate classes or plugins, that also improves usability and testability. As well it increases idiomaticness, as a fully idiomatic build script is fully declarations l declarative and contains no iterative logic.
So yes, that could make sense. But for quick and dirty you can also register the outputs using outputs.file... and similar. If you need all outputs of a task as input to another, you can then also just use the task (resp. It’s provider) as input, if you need only a subset of the tasks outputs, you would use the task provider map ed to the subset for example.
I created smaller tasks and rely on the built-in ones as far as possible. For the metadata extraction I will create a custom task class with proper input and output definitions that will solve other issues as well.