The two plugins would only be grouped together for better readability. However, when I tried to do so, it would obviously tell me that thatoneplugin() couldn’t be found. I assumed that touching the extension.create would fix it:
The dot has no special meaning. In this case, you created an extension with name mypluginstuff.thatoneplugin, but when you type the same thing in the script, Gradle sees it as:
get mypluginstuff property from project
get thatoneplugin property from mypluginstuff
As mypluginstuff does not exist, step 1 fails.
If you want the nested syntax, you should either implement methodMissing on MyPluginExtension and hendle closure parameters (read about using closure delegates). Or if you know what nested ‘sub-things’ you want to support, declare normal methods taking closure as parameters.
That looked like a straightforward solution, which I was looking for. However, it doesn’t seem to work.
* What went wrong:
Execution failed for task ':sometask'.
> Could not get unknown property 'thatoneplugin' for root project 'NestedTestGradle' of type org.gradle.api.Project.
According to the page you linked to, you wrote one “extensions” too much on your second line, but even without that I get the same error.
As type I’ve used the same one, since that’s also what the page tells you, and I wouldn’t even know what else to use.
Yes, the extra ‘extensions’ is not needed, I just prefer it as a matter of personal style since it disambiguates the task and extension containers, and all the other properties available from the project.
Here’s a quick test:
class Outer {
String a
}
class Inner {
String b
}
extensions.create( 'outer', Outer )
extensions.outer.extensions.create( 'inner', Inner )
outer {
inner {
b 'world'
}
a 'hello'
}
task ab << {
println "${outer.a} ${outer.inner.b}"
}
In that case let me spell it in simpler terms: Gradle does not support “Multi-level DSL for plugin extension” in the way you envision.
It is achievable, if you do some mumbo jumbo that you are not prepared to do.
That is not the worst though, the worst is that such a plugin would be running counter to the existing best practices and conventions, likely confusing your users and burdening the maintainers.
In summary: don’t do it. If you want your plugins to be visually grouped - use comments.
Nice, got it work! At first it was being annoying once more, because I tried to migrate it to a plugin and forgot to add the “project” reference before “outer.a”. Thanks a ton, bro.
Out of pure curiosity though, now I got something like this:
println "{$project.outer.inner.a}"
Which tends to become rather lengthy unmaintainable after multiple usages, especially if I should consider to add a third level of nested DSL. Got any idea how to make that more readable?
What is so bad about that, though? Genuinely curious, since I’m still new.
From what I could gather so far, lots of plugins and Gradle functionality has nested functionality, which was more or less what I tried to achieve and also did, thanks to Chris’ suggestion.
But I’m not trying to add plugins to other plugins, it’s all contained within the same plugin source code file. The only thing outside is the actual curly bracket DSL.
However, your approach—if definitely more confusing to me, sadly—seems to be maintainable too. Maybe I can get it to work too, though so far I’m perfectly happy with Chris’ suggestion.
My approach is doing what the extension mechanism would do in a statically typed way.
The getter is needed so users can call
outer.inner.someThing = 'foo'
The closure method is for the curly brace syntax:
outer {
inner {
someThing = 'foo'
}
}
If you are happy with using extensions, that’s perfectly fine I just wanted to give you the alternative. We prefer that one as it makes it easier for others to discover the API.
I was a little confused what you meant by “we prefer”. It was only then that I noticed your Core Dev tag. Now I’m definitely convinced to give it another try.
I added some more comments to the code to explain what each piece does. As I said, this is just the preference in the Gradle code base, because it gives you an API that is easier to navigate with auto completion in the IDE for instance.
// for curly-brace syntax
void inner(Closure configuration) {
ConfigureUtil.configure(inner, configuration)
}
//same as above, but better for Java/Kotlin clients
void inner(Action<? super Inner> configuration) {
configuration.execute(inner)
}
Are both versions of inner() required for Groovy, Java, and Kotlin support, or will just ‘void inner(Action<? super Inner> configuration)’ work for all three? I think I read somewhere that Gradle will decorate the class with the Closure variant of the method based on the Action variant, but I haven’t had a chance to actually try it out.
We do add the Closure version for objects created by the Gradle Instantiator, e.g. when you call createExtension. That doesn’t work when you call a constructor yourself.
We should probably do it via bytecode manipulation at class loading time instead to make it always work.