Gradle 8.9 dependency issues

I am trying to update from Gradle 7.4 to Gradle 8.9 and have run into an issue I do not know how to solve.

I have two tasks, call them taskA and taskB in a module call it modM, and I am using a 3rd party plugin, the Android Gradle Plugin (AGP)

taskA generates some files that are consumed by tasks dynamically generated from AGP. This all used to work fine in 7.4 but in 8.9 Gradle fails hard saying

Task ':modM:generateReleaseLintVitalModel' uses this output of task ':modM:taskA'

I am able to work around this by adding a dependsOn statement as so

afterEvaluate {
  tasks.generateReleaseLintVitalModel.dependsOn taskA
}

All well and good until I go to execute taskB.

taskB makes a small change to an xml file. Nothing more. When I execute taskB Gradle fails hard saying

Could not get unknown property 'generateReleaseLintVitalModel' for task set of type org.gradle.api.internal.tasks.DefaultTaskContainer

taskA is executed as part of the assemble task, taskB is executed independent, and before, the assemble task, but I need to execute taskB before taskA.

This worked just fine in 7.4 and I have no idea how to fix it in 8.9.

Actually, it did not “work just fine”.
It was at the very best flaky and maybe sometimes worked, maybe if you were lucky often worked, but was not in any way reliable.
Gradle 7 (maybe in a later version) added some detection that showed a warning and in Gradle 8 it was made a hard error.

How to properly solve it I cannot tell you as you shared too little information.
Using afterEvaluate { ... } usually is one of the worst “solutions”. The main earnings you get from using it are timing problems, ordering problems, and race conditions. Using it to “fix” some build problem is like using Platform.runLater or GuiUtilities.runLater to “fix” a GUI problem which indeed usually just shifts the problem to a later, harder to reproduce, harder to find, and harder to fix point in time, and just is symptom treatment.
And likely any explicit dependsOn where on the left-hand side is not a lifecycle task is a code smell and usually a sign that you do something wrong, even though that error suggests you add it, but that is a bad advice and also just symptom treatment.
Most often when you get this error it means one of two things. Either you have tasks with overlapping outputs, which is a very bad idea and highly disouraged as it also causes quite a few other problems. Or you did not properly wire task outputs to task inputs but configured paths somewhere and then indeed miss the task dependencies that would be automagically there if you would have wired things properly.

I was afraid this was going to be the answer.

This is fair. I understand that the quality of a response depends upon the quality of the question. And my initial question exposes my lack of understanding of Gradle magic. It’s hard to describe what you don’t know.

So I’ll try to elaborate a bit.

The Gradle complaint is:

Reason: Task ':ModuleFoo:generateReleaseLintModel' uses this output of task ':ModuleBar:copyToAssets' without declaring an explicit or implicit dependency.

copyToAssets is our task, generateReleaseLintModel is a task defined by the AGP. Note that not specified in my original post, they are in different modules. And I do indeed think I now understand the problem in that copyToAssets in ModuleBar is writing into a directory in ModuleFoo.

Based upon your comment regarding wiring task outputs to task inputs I tried to declare cross project dependencies as described here: Sharing outputs between projects

Namely in ModuleBar, the producer, I did this:

def copyToAssets = tasks.register('copyToAssets', Copy) {
    dependsOn 'someTaskThatCreatesClasses'
    from file("${rootProject.buildDir}/foo/bar.jar")
    into file("${project(':ModuleFoo').projectDir.path}/src/main/assets/java/")
}

configurations {
    someFiles {
        canBeConsumed = true
        canBeResolved = false
    }
}

artifacts {
    someFiles copyToAssets
}

And in ModuleFoo, the consumer, I did this:

configurations {
    someFiles {
        canBeResolved = true
        canBeConsumed = false
    }
}

dependencies {
    // other stuff
    someFiles(project(path: ':ModuleBar', configuration: 'someFiles'))
}

Gradle, however persists in throwing the same error.

Can you please elaborate on what you mean by wiring task outputs to task inputs? Particularly in the context where the producing task is in one module, and the consuming task is a generated task in a different module, where the generated task is coming from a 3rd party plugin, namely the Android Gradle Plugin?

And I do indeed think I now understand the problem in that copyToAssets in ModuleBar is writing into a directory in ModuleFoo.

Yes, this for sure is part of your problem, thats very rarely (if ever) a good idea.

I tried to declare cross project dependencies as described here

Great, unfortunately you did not fully follow it properly.
Your copyToAssets still copies the files to the other module’s directory.
Also, any explicit dependsOn where on the left-hand side is not a lifecycle task like in your line 2 is a code smell and usually a strong sign you do something wrongly.

If the someTaskThatCreatesClasses is the one creating bar.jar, remove the whole copyToAssets task and use someTaskThatCreatesClasses in the artifacts block.

If you then also actually use the someFiles configuration in the consumer project to get the file, you should be closer to the proper way. (Actually not fully, as for example the someFiles configuration in the consumer is not optimal as a configuration should be either for consumption or resolution or declaration, but not for two or three of these aspects like in what you showed)