Extension DSL inheritance

I’m working on a plugin which exposes a number of extensions, depending on which parts of the plugin you apply, and I want the extensions to “extend” the base extension. First I thought I had to implement missingMethod() or manually amend the base extension as other plugins were applied, but to my surprise I could just nest them in my build script and it worked out of the box.

class BaseExtension {
    String baseProp
}

class FooExtension {
    String fooProp
}

class MyBasePlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create('base', BaseExtension)
    }
}

class MyFooPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.plugins.apply(MyBasePlugin)
        project.extensions.create('foo, FooExtension)
    }
}

Then in build.gradle

apply plugin: MyFooPlugin
base {
    baseProp = 'test'
    foo {
        fooProp = 'other'
    }
}

Using the fooProp later in the plugin works fine. Does the ExtensionAware system fall back to parent scope when base doesn’t actually contain foo, or how does this work? (I can also put the foo closure outside of the base closure, and it works fine.

I can’t find any examples or specifics in the documentation about this use case, so I wonder if it’s accidental, or intended.

In this case FooPlugin is optional, and some users might only care about MyBasePlugin, or other sub-plugins, (BarPlugin, etc.), hence the multiple extensions.

Finally, if I wanted to prevent configuration of foo outside of base, what would I have to do?

The pattern of how a Closure resolves a reference belongs to Groovy, not Gradle. Read about this, owner, delegate scopes in Groovy if you want to know the exact details.

That’s not what you have as the BaseExtension and FooExtension are not related in any way in your example. They might as well be for unrelated functionality.

Create the FooExtension on the BaseExtension instead of on project.

Thanks, I read about the Closure scoping rules a while ago, just didn’t think about that being effective here. Very interesting system actually.

I’ve tried creating extensions on the extension container of another extension, but they don’t seem to allow for the same nice Closure style configuration. I ended up with something more like:

base.foo {
}

rather than

base {
    foo {}
}

I understand this should be possible to work around by adding the right methods onto base, but I tried a few ways and none played well with the decorated versions of BaseExtension, so I dropped that route at the time. Perhaps I just need to be more persistent, and dig further into the mechanics of Groovy.

You shouldn’t need to work around anything if you’re using the extension mechanism across the board for your plugin properties. The situation might be different if you were trying to code MyBasePlugin to react to what could be added to BaseExtension by MyFooPlugin, as opposed to MyFooPlugin being able to handle it.

Taking your original example, if I change the creation of FooExtension to

project.extensions.base.extensions.create('foo', FooExtension)

I can configure the FooExtension the same way as you did in build.gradle, but foo is no longer available outside the context of base { }.

Thanks, I tried it again, and it works as advertised. I must have had some other error that prevented it from working previously.