Reputation: 3
I was looking at the closure scopes and found the output counter intuitive, this code was in build.gradle
file
plugins {
id 'java'
}
sourceSets {
println 'source sets closure scopes this,owner, delegate'
println this.toString() // output: root project 'multigradle'
println owner.toString() // output: DynamicObject for SourceSet container
println delegate.toString() // output: SourceSet container
}
Why the owner
is not equal to this
, does gradle clone the closure?
PS : For anyone who will read it in future 'multigradle' is my gradle project name.
Upvotes: 0
Views: 1215
Reputation: 4482
TL;DR;
Essentially within the gradle source there is a method something like this:
public Object sourceSets(Closure closure) {
// do stuff like configuring the closure
closure.call()
}
so when you call:
sourceSets {
some code
}
(which incidentally is the same as calling sourceSets({ some code })
, just with the parens removed which is ok in groovy)
"some code" is not executed immediately when the sourceSets
method is called. Gradle can choose to execute it whenever they decide it's time. Specifically, they can (and do) configure things like owner and delegate before actually executing the closure.
Longer version
Turns out the sourceSets
method in your build.gradle
file is actually added by plugins such as the java/kotlin/groovy plugins.
As an example we can look at the java plugin and the DefaultJavaPluginConvention class which has the following code:
private final SourceSetContainer sourceSets;
@Override
public Object sourceSets(Closure closure) {
return sourceSets.configure(closure);
}
this is the method that gets called when you type sourceSets { ... }
in your build.gradle
file. It gets handed the closure and proceeds to hand it off to the configure
method of the source set container. Note that we have not executed the closure yet, we are just passing it around as a non-executed block of code.
If we dig a little, we find the configure
method in the AbstractNamedDomainObjectContainer class:
public AbstractNamedDomainObjectContainer<T> configure(Closure configureClosure) {
ConfigureDelegate delegate = createConfigureDelegate(configureClosure);
ConfigureUtil.configureSelf(configureClosure, this, delegate);
return this;
}
(SourceSetContainer
is an interface and the implementing class inherits from AbstractNamedDomainObjectContainer
...this is the right configure
method)
Where the ConfigureUtil has the following code:
public static <T> T configureSelf(@Nullable Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
if (configureClosure == null) {
return target;
}
configureTarget(configureClosure, target, closureDelegate);
return target;
}
private static <T> void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
if (!(configureClosure instanceof GeneratedClosure)) {
new ClosureBackedAction<T>(configureClosure, Closure.DELEGATE_FIRST, false).execute(target);
return;
}
// Hackery to make closure execution faster, by short-circuiting the expensive property and method lookup on Closure
Closure withNewOwner = configureClosure.rehydrate(target, closureDelegate, configureClosure.getThisObject());
new ClosureBackedAction<T>(withNewOwner, Closure.OWNER_ONLY, false).execute(target);
}
where the relevant part is the call to the groovy Closure rehydrate method which according to docs does the following:
Returns a copy of this closure for which the delegate, owner and thisObject are replaced with the supplied parameters. Use this when you want to rehydrate a closure which has been made serializable thanks to the dehydrate() method.
Only on the last line of the configureTarget
method does gradle call execute
on the action created to represent the closure. So the execution of the closure is done after the owner, the delegate and the this pointer have been configured according to gradle needs.
Upvotes: 1