Zip dynamically generated DirectoryProperty

I’m writing a custom Task that writes files into a output directory. In practice, it’s something as simple as annotating a DirectoryProperty with get:OutputDirectory.

However, in my special case, the output directory is a path dynamically generated by a third party tool executed by my Task.

Hence, I’m defining my plugin interface as such:

@get:Internal
abstract val reportDir: DirectoryProperty

It seems to work fine. I can reference it from other Tasks:

val customTask = project.tasks.register("customTask", MyCustomTask::class.java)

project.tasks.register("testReadDir") { task ->
  task.doLast {
    project.logger.lifecycle("REPORTDIR: ${customTask .get().reportDir.file("file.txt").get().asFile}")
  }
}

Output:

> Task :testReadDir
REPORTDIR: C:\workspace\model-test\ws\Project\TestReports\file.txt

The issue is that I’m unable to zip that Directory. No matter what I do, it’s trying to resolve the reportDir in the configuration phase.
The most obvious certainly-at-config-phase attempt is this one:

project.tasks.register("compressReports", Zip::class.java) { task: Zip ->
  ...
  task.from(customTask.get().reportDir)
  ...
}

But I also tried these and a bunch of other combinations…

task.from(project.provider { customTask.get().reportDir })
task.from(project.provider { customTask.get().reportDir.get().asFile.absolutePath })
task.from(project.objects.directoryProperty().dir(customTask.get().reportDir.get().asFile.absolutePath))

But I always get

Cannot query the value of task ':customTask' property 'reportDir' because it has no value available.

I’m probably missing something very simple…
How to zip it?

Independent of the problems you describe, doing it like that is most probably a very bad idea.
Well, unless you also mark the task as @UntrackedTask and thus have it never up-to-date or cacheable.
Because by having that property @Internal instead of some @Output..., it - well- is missing as output and so the up-to-date logic can never be correct and it can never be cacheable.

But even if you mark the task as untracked, then also the property does not have an implicit task dependency as it should have and you can not simply use the task as input for other tasks and thereby automatically use all the outputs. (Assuming this is the only output)

Also using get() at configuration time like in your “most obvious” attempt is a bad idea.
Whenever you use get() at configuration time you almost certainly do something wrong.
By that you break task-configuration avoidance for that task.
And you also do not get the necessary task dependency.
Typically you would just do from(customTask) if the task would properly declare its outputs and you want to zip all outputs,
or if the task has multiple output properties and you only want to zip one from(customTask.flatMap { it.reportDir }).
Which of course does not work in your case, because you did not declare the property as output and so it misses the necessary task dependency so you would instead do from(customTask.map { it.reportDir.get() }).

Also all your other tries probably do not work properly because you strange constructs and additionally missing task dependency.

If possible, my recommendation would be to make the reportDir independent from the tool you call, by setting the reportDir and in the task implementation sync or move the files from the directory where the tool has put them to the configured directory for example.

1 Like

yeah! The “strange constructions” were trial-and-error :smiley: I’m not proud of them :smiley: I’ve used the Zip Tasks many time, but it’s the first time I have this weird use case.

We barely use Gradle’s own caching mechanism, because the tools we wrap have an “update” decision of their own. We are technically not supposed to overrule then. In fact, we use a Gradle Project as sort of a side-car to the actual project to automate some executions, but we aren’t allowed to turn the project itself into a Gradle Project. I know, it’s weird…

In this particular case, indeed, we are allowed to do it. Alternatively, we can also set the @OutputDirectory reportDir with the parent path used by the tool, which is known to us in advance. But this way we are just pretending it’s an input configuration, but we can’t ever change it.
So I’ll probably stick to your suggestion.

Thank you!

We barely use Gradle’s own caching mechanism

Well, even without, caching and without up-to-dateness, inputs and outputs are important for the implicit task dependencies, unless you also don’t really use them. Could well be from what you said. :smiley:

But this way we are just pretending it’s an input configuration, but we can’t ever change it.

Just that it is declared as @OutputDirectory does not mean it is “configurable”, it just declares what the outputs of the task are.
You can for example also after setting the value call finalizeValue() so that no further change can be done to the property.

1 Like