Felix de Souza
10/23/2024, 2:30 PMtasks.withType(TaskType.class).all(task -> methodThatRegistersAnotherTask(task)
to:
tasks.withType(TaskType.class).configureEach(task -> methodThatRegistersAnotherTask(task))
and I’m met with:
Caused by: org.gradle.api.internal.AbstractMutationGuard$IllegalMutationException: DefaultTaskContainer#register(String, Class, Action) on task set cannot be executed in the current context.
From digging around, the DefaultTaskContainer seems to pass in the mutation guard for the CrossProjectConfigurator and that then means I can’t create a task while configuring another one. For the most part, I’m not even trying to configure the task, I’m just trying to use the listening capabilities of TaskContainer and NamedDomainObjectContainer etc. so I can create more tasks. This is also happening within the same project. It feels like it’s particularly strict, and that I’m forced to either use .all, afterEvaluate or know upfront what tasks are present for me so I can create new tasks.
I had previously tried to add it to a separate NamedDomainObjectSet but of course, once the listeners are set up, the configuration actions are called within the overall task configuration action resulting in the above error message.
Am I stuck using .all ?Adam
10/23/2024, 2:56 PMall {} to eagerly initialise is fine for this purpose, although it's best avoided.
Possibly you could rearchitect to avoid it? E.g. create a TaskTypeSpec that configures all aspects of TaskType. Create a NamedDomainObjectContainer for the `TaskTypeSpec`s. In the upstream plugin, do taskTypeSpec.all {} to register all the tasks proactively. And in the downstream plugin you can lazily configure the specs, or eagerly react if necessary (e.g. to register the other tasks).Thomas Broyer
10/23/2024, 3:48 PMVampire
10/23/2024, 4:34 PMdoLast action instead of registering another task, or reacting to the original cause for the task, like handling source sets instead of compile tasks directly and so on.Felix de Souza
10/24/2024, 1:22 PMyeah, usingIt works, but doesn’t really feel fine. We’re trying to reduce our footprint, but theto eagerly initialise is fine for this purpose, although it’s best avoided.all {}
.all kinda blows up in our face. I have separate timing issues elsewhere, but this certainly doesn’t help
Possibly you could rearchitect to avoid it? E.g. create aThat is a thought but feels like I’d be passing some interface that looks likethat configures all aspects ofTaskTypeSpec. Create a NamedDomainObjectContainer for the `TaskTypeSpec`s. In the upstream plugin, doTaskTypeto register all the tasks proactively. And in the downstream plugin you can lazily configure the specs, or eagerly react if necessary (e.g. to register the other tasks).taskTypeSpec.all {}
Task around everywhere, and still feels a bit meh imo.
You’ll want to register the task when a given task is registered, not when it’s configured (which means it’s being used, which happens after the task graph has been computed)
https://github.com/gradle/gradle/issues/25262 contains a workaround using an internal API, but IIRC there’s another workaround using only public APIs that was once shared in this slack 🤔This explanation makes a lot of sense and has made things click for me. What you’re mentioning + the attached issue is exactly what I want.
I don’t remember any public API way to do it.
Depending on exact use-case there might be alternatives though, like adding aI think we prefer to avoid theaction instead of registering another task, or reacting to the original cause for the task, like handling source sets instead of compile tasks directly and so on.doLast
doLast method because it messes with attribution in build scans. We don’t necessarily maintain all the plugins that react to tasks that we do maintain.
I think your approach re reacting to the original cause similar to the TypeSpec piece above does make sense, but that feels like something typically planned for. Lots of other plugins that we don’t actively write but our devs do will do this style of task reactivity. Doing what you’re saying re introducing some intermediate type would end up proliferating to a lot of places.
I’m somewhat curious about downsides to adding a method to the `TaskContainer`: tasks.whenTaskRegistered(String|Spec<String>|Class|String & Class, (name&type OR TaskProvider) -> action) etc. Then you know it’s safe to call .named and whatever you want to do within action. Basically exposing the method linked and/or going above and making it nicer to use. I’m going to have a harder time convincing devs who write plugins to add all the aforementioned scaffolding to avoid an all . I also think it’d make it a lot more straightforward to flag via the deprecation/problems api that .all is not ideal on a TaskContainer and there is a relatively simple alternative.