Weird interaction between `allprojects` and `evaluationDependsOn`

I have a multi-project build with three projects: a, b, and c.

They’re all configured by the root build.gradle, which looks like this:

allprojects {
    project.afterEvaluate {
        task canary
    }
}

If I run gradlew canary, I get this output:

:canary UP-TO-DATE
:a:canary UP-TO-DATE
:b:canary UP-TO-DATE
:c:canary UP-TO-DATE

Not too shocking. Now, let’s say I change my build.gradle, as such:

project(':a') {
    evaluationDependsOn(':b')
    evaluationDependsOn(':c')
}

allprojects {
    project.afterEvaluate {
        task canary
    }
}

Now, when I run gradlew canary, I only get this:

:canary UP-TO-DATE
:a:canary UP-TO-DATE

It’s as though the canary task never existed for b and c, and there are no warnings.

But, if I put the allprojects block at the top, then everything goes back to working as expected. This is all in gradle 3.3.

I spent a couple hours banging on this issue, but the evaluationDependsOn and afterEvaluate blocks were in plugin code, so it was pretty difficult to figure out what was going on.

I sort of understand how a is causing b and c to eagerly evaluate, but I don’t understand why b and c don’t get to finish their own evaluation first. If there’s no way to fix this issue, it seems there should at least be a loud warning when someone makes an afterEvaluate call which will never execute.

You’re adding to the afterEvaluate closures too late… they’ve already been evaluated. Gradle should help out here by throwing an exception I guess.

Here’s some code to show the issue:

allprojects { p ->
	println "[1] Adding afterEvaluate for $p.name"
	project.afterEvaluate {
		println "[1] afterEvaluate $p.name"
	}
}

project(':a') {
	println "[2] Adding evaluationDependsOn"
	evaluationDependsOn(':b')
	evaluationDependsOn(':c')
}

allprojects { p ->
	println "[3] Adding afterEvaluate for $p.name"
	project.afterEvaluate {
		println "[3] afterEvaluate $p.name"
	}
}

Output of gradle tasks

[1] Adding afterEvaluate for root
[1] Adding afterEvaluate for a
[1] Adding afterEvaluate for b
[1] Adding afterEvaluate for c
[2] Adding evaluationDependsOn
[1] afterEvaluate b
[1] afterEvaluate c
[3] Adding afterEvaluate for root
[3] Adding afterEvaluate for a
[3] Adding afterEvaluate for b
[3] Adding afterEvaluate for c
[1] afterEvaluate root
[3] afterEvaluate root
[1] afterEvaluate a
[3] afterEvaluate a

Gradle should help out here by throwing an exception I guess.

That would be awesome. I agree it’s easy to see in these examples, but the actual bug for me was caused deep within plugin code. Very hard to figure out, and very confusing that changing the application order of unrelated plugins fixed / broke the build.

Also, I don’t understand why the project(':a') block has to cause early evaluation. If a has its own build.gradle, it will be read properly before evaluation, but the rest of root buildscript won’t. I don’t understand why gradle is able to delay evaluation long enough to read a’s own buildscript, but not long enough to finish reading the root buildscript.

I raised an issue on github