Hi all, I have a custom Gradle task which download...
# community-support
r
Hi all, I have a custom Gradle task which downloads a file into the project's build directory. This task does not have any inputs, but I'd still like the outputs to be cached. At the moment, Gradle is skipping it due to NO-SOURCE. How can I get the task to run and be cachable even if it has no inputs? Thanks!
v
If the task is skipped with
NO-SOURCE
, it also did not download your file, because it was skipped. And if it is skipped with
NO-SOURCE
it means you do have inputs declared with "skip when empty". Whether you really want your task cacheable you should think twice. Each time you download you could get a different result. If you make the task cacheable, you always get the cached result.
r
I do want it cachable, it's always downloading the same file. But I guess I would like the input to be the URL of the file it's downloading, that's a good point. I'm creating the task via
project.tasks.register
, I guess I could create an input file containing the URL.
v
That's not necessary. Inputs can also be strings or URL instances.
👍 1
But still, if the task is
NO-SOURCE
it did not run anyway and should have input files defined that are empty. How exactly do you declare the task in whole?
r
It's something like:
Copy code
return project.tasks.register("downloadCompilerPackage") {
   doFirst {
      ...
   }
   doLast {
      ...
    }
}
I was surprised too about how it assumes that it should have sources
v
"something like" does not really match the asked for "exactly" ;-) Especially, how did it download anything at all, when it was skipped? You probably really need to provide an MCVE to speak about details.
r
Hah, you're right. I don't mind sharing the whole thing:
Copy code
return project.tasks.register("downloadKaitaiCompiler") {
            doFirst {
                locksService.getLock("downloadKaitaiPackage").withLock {
                    pluginResources.kaitaiRootDirectory.mkdirs()
                    if (!pluginResources.kaitaiRootCompilerZip.exists()) {
                        project.uri("...")
                            .toURL()
                            .openStream()
                            .use { inputStream -> inputStream.copyTo(FileOutputStream(pluginResources.kaitaiRootCompilerZip)) }
                    }
                }
            }
            doLast {
                locksService.getLock("extractKaitaiPackage").withLock {
                    if (!pluginResources.kaitaiRootCompilerDirectory.exists()) {
                        project.copy {
                            from(project.zipTree(pluginResources.kaitaiRootCompilerZip))
                            into(pluginResources.kaitaiRootCompilerDirectory)
                        }
                    }
                    val classLoader: ClassLoader = KaitaiPlugin::class.java.classLoader
                    PluginUtils.copyResourceFile(
                        classLoader,
                        "io/model9/backup/gradle/kaitai/kaitai.sh",
                        pluginResources.kaitaiScriptFile.absolutePath,
                        true)
                }
            }
}
Might be a bit hackish. I'm using a service to make sure this task does not run in parallel where it shouldn't.
v
Some own lock-service is a bit overkill, unless you really need this fine-grained locking. Gradle has something for it built-in. You create a shared build service, configure its max parallel use to 1 and declare all tasks that should be mutual exclusive to use that service, then only one can run at a time. But anyway, with what you have shown, this task will never be skipped, for example not with
NO-SOURCE
, so you probably configure more to it later.
If you for example add outside
doFirst
and
doLast
the line
inputs.files().skipWhenEmpty()
, then it will indeed always be skipped with
NO-SOURCE
and never download the file
r
That could be it, in another branch I've added the following to try to get the task to cache:
Copy code
outputs.dir(pluginResources.kaitaiRootDirectory)
outputs.cacheIf { true }
Sorry for the possible mixup. Next, I'll try declaring the task's inputs as the URL and the outputs as above to try to get the task to cache and never skip. I'll also do as you suggest with regards to to the service usage. As always, many thanks for the help @Vampire. Your assistance is really invaluable. I'll report back with findings.
👌 1
v
NO-SOURCE
is about inputs, not outputs. If there were not inputs defined, those two lines should make the task cacheable indeed afair
🤔 1
r
I'll have another go at it.
It seems to be working well now. The custom BuildService with a maxParallelUsage of 1 is a nice touch.
👌 1
I do have a follow up question about the following:
Copy code
PluginUtils.copyResourceFile(
                        classLoader,
                        "io/model9/backup/gradle/kaitai/kaitai.sh",
                        pluginResources.kaitaiScriptFile.absolutePath,
                        true)
This is part of a task which copies a resource from the plugin's classpath into the project's build folder. Is there a way to define its inputs properly? Or should I just never cache it to make sure that changes in the script resource will always be applied?
v
The plugin's runtime classpath is automatically an input for the task, so if the resource changes, the task is rerun anyway. Unless you do bad things like
outputs.upToDateWhen { true }
or similar, so to be on the safe side, just try it.
If you run with
-i
you are also told exactly why a task is out of date
r
Great. Thanks Vampire!
👌 1