Why does Gradle insist on creating all the directo...
# community-support
s
Why does Gradle insist on creating all the directories that I specify as output directories of a task? I have a task where one instance may put some files there, and another instance may not, but it’s difficult for me to know in advance which one will use which directories. So I specify the union set of all these directories as outputs. But now I have to tolerate all these empty output directories “helpfully” created by Gradle sitting around polluting my file system view…
m
I actually like this behaviour. For snapshotting purposes, the directory should always be present.
s
Why should it always be present?
m
The outputs are pretty much an implementation detail. Ultimately, I wish Gradle allocates the path automatically as well
If a task has an output, I like to think that it's always present. If not, this is more state to represent
s
Well in my case that wouldn’t work because the outputs are decided by the app my task is running.
“more state to represent” sounds like an implementation concern to me - I care much more about having fewer extra folders than a few bytes more state deep in Gradle internals.
m
It's mostly about API design
s
not just API, user experience too
m
Is there even
@Optional
for outputs?
s
that would have different semantics
that would mean something like “the user doesn’t have to set this output path” but that’s not what I need, I need something like “this output directory isn’t always written”
m
The parallel I make is nullability. I want things to be never nullable because it's less
if
s in my code. Similarly I want some consistency in the tasks inputs/outputs.
But it comes down to personal preference ultimately, which is why there are still gazillions programming languages 😄
s
It’s always nice if it works that way, yes, but I’m creating Gradle tasks to encapsulate/wrap an existing application that I can’t modify to 100 % fit this idea. In many other ways Gradle has been excellently flexible and that’s why I chose it and I’m happy with my choice, but this is an annoyance, even though minor.
m
Yea that's the thing, I want Gradle to stop being flexible and give me so many footguns 😂 Just be opinionated about tasks and make everything cacheable by default, using relative path sensitivity, enforce non-overlapping outputs, etc...
s
That’s what #C06JG95HREY is going to be, I believe
🤞 1
m
To some extent yes although it's mostly the frontend from what I have seen
Back to the "create output folder issue", I guess there could be an advanced API that allows modeling the outputs differently and exposes footguns for those willing to do custom things. Might be worth filing an issue if you feel strongly about the extra empty dirs.
t
You could configure the various directories as
@Internal
properties and have an output property whose value is computed by the task depending on what it will do / have to do.
v
I don't think you can calculate the output property at task execution time. That would not work with up-front checking up-to-date state.
But if you cannot disable the automatic creation of output directories, you can at least in the task implementation simply iterate over the output directories and delete them before you call your external tool which will then leave you with only the output directory created by the tool.
s
hmm that could be a nice workaround but wouldn’t it mess with Gradle’s internal state or something? after all, it created those directories for some reason so it probably assumes they will exist?
v
As far as I remember, it shouldn't be a problem. And having a quick look at the Gradle code, I think the creation is not conditional, so deleting them might indeed be the only way. You'll see whether it complains then when snapshotting the outputs after the task executed. 🙂
If it does, I'd maybe consider that a bug
t
Just tried it, it works, and is compatible with configuration cache:
Copy code
abstract class MyTask : DefaultTask() {
    @get:Internal
    abstract val fooDir: DirectoryProperty
    @get:Internal
    abstract val barDir: DirectoryProperty
    @get:Input
    abstract val useFoo: Property<Boolean>
    @get:OutputDirectory
    val outputDirectory: DirectoryProperty
        get() = if (useFoo.getOrElse(false)) fooDir else barDir
    @TaskAction
    fun run() {}
}

tasks {
    register<MyTask>("fooOrBar") {
        fooDir = layout.buildDirectory.dir("foo")
        barDir = layout.buildDirectory.dir("bar")
        useFoo = false
    }
}
v
Yeah, that is not calculated at execution time but configuration time.
And if he would be able to cacluclate it up-front, also a single output directory property would be enough
That's why I assumed he cannot calculate up-front to which directory the tool will write Just which are the possible output directories
s
right, my problem is, this ‘useFoo’ is not something the users specify in Gradle, it’s something that’s specified somewhere deep in those files that the task is processing.
and if I were to tell users to specify it in Gradle as well, it would make the task harder to use.
t
Well, this works too:
Copy code
abstract class MyTask @Inject constructor(
    private val providers: ProviderFactory
): DefaultTask() {
    @get:Internal
    abstract val fooDir: DirectoryProperty
    @get:Internal
    abstract val barDir: DirectoryProperty
    @get:InputFile
    abstract val inputFile: RegularFileProperty
    @get:Internal
    val inputFileContent: String by lazy { inputFile.get().asFile.readText() }
    @get:OutputDirectory
    val outputDirectory: Provider<Directory>
        get() = providers.provider { if (inputFileContent == "foo") fooDir.get() else barDir.get() }
    @TaskAction
    fun run() {}
}

tasks {
    register<MyTask>("fooOrBar") {
        fooDir = layout.buildDirectory.dir("foo")
        barDir = layout.buildDirectory.dir("bar")
        inputFile = layout.projectDirectory.file("input")
    }
}
Of course determining whether you use
fooDir
or
barDir
needs to be relatively lightweight, as you pay that price every time, to compute whether the task is up-to-date or not, to decide whether to run it or not