What is up with this forum and its silly title problem?

Real title (that the forum software does not like): How to use PolymorphicDomainObjectContainer?


Probably I am just missing some dark magic, but I am not able to get PolymorphicDomainObjectContainer references working the way that the Gradle documentation says they should. E.g. I have the following:

interface Extension {
    ...
}

class StandardExtension implements Extension {
}

class HibernateOrmExtension implements Extension {
}

/**
 * A Grade extension (not my Extension contract) registered as "myDsl"
 */
class MyDsl {

    private final PolymorphicDomainObjectContainer<Extension> extensions;

    MyDsl(Project project) {
        ...

        this.extensions = project.getObjects().polymorphicDomainObjectContainer( Extension.class );

        // register factories
        extensions.registerFactory( HibernateOrmExtension.class, ... );
        ...

        // register the "default" factory
        extensions.registerFactory( Extension.class, ... );
    }

    public void extensions(Action<PolymorphicDomainObjectContainer<Extension>> action) {
        action.execute( extensions );
    }

    /** 
     * Solely here to be able to disambiguate `extensions` as-in Gradle's woven ExtensionContainer. 
     * Oddy, Kotlin scripts seem to not have problems understanding that `extensions {}` refers to
     * {@link #extensions}.  Groovy builds for whatever reason try to map that to 
     * `ExtensionContainer#getExtensions()`.  Grr...
     */
    public void myExtensions(Action<PolymorphicDomainObjectContainer<Extension>> action) {
        action.execute( extensions );
    }
}

Given all of that, based on the documentation, I believe I should be able to say something like:

myDsl {
  // Groovy
  myExtensions {
    worksFine { ... }
    doesNot( HibernateOrmExtension ) { ... }
  }
}

However, that leads to:

Caused by: org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method doesNot() for arguments [com.github.sebersole.gradle.quarkus.extension.StandardExtension@5b647277, build_efel1wrxisx65esciz2niim8a$_run_closure4$_closure6$_closure7@1b5d6573] on Extension container of type org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer.
	at org.gradle.internal.metaobject.AbstractDynamicObject.methodMissingException(AbstractDynamicObject.java:182)
	at org.gradle.internal.metaobject.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:86)

Any idea what I am doing wrong here?

There seems to be some things really strange highlighted in that error message. How does it already have an Extension reference (what it thinks is the first arg)?

So I assume that PolymorphicDomainObjectContainer simply do not work for end-users.

Sadly it is an incubating feature, so I guess I cannot complain too much. But man it really stinks that an exposed feature (incubating or not) simply does not work or do anything

Here’s something I whipped up in a build.gradle, hopefully it helps:

abstract class MyNamed implements Named {
    private final String name
    abstract Property<String> getZero()
    @javax.inject.Inject
    MyNamed(String name) {
        this.name = name
    }
    @Override
    String getName() {
        return name
    }
}
abstract class MyNamedOne extends MyNamed {
    abstract Property<String> getOne()
    @javax.inject.Inject
    MyNamedOne(String name) {
        super(name)
    }
}
abstract class MyNamedTwo extends MyNamed {
    @javax.inject.Inject
    MyNamedTwo(String name) {
        super(name)
    }
}

class MyExt {
    private final ExtensiblePolymorphicDomainObjectContainer<MyNamed> elementContainer

    @javax.inject.Inject
    MyExt(ObjectFactory objectFactory) {
        elementContainer = objectFactory.polymorphicDomainObjectContainer( MyNamed )
        elementContainer.registerFactory( MyNamed, { objectFactory.newInstance(MyNamed, it) } )
        elementContainer.registerFactory( MyNamedOne, { objectFactory.newInstance(MyNamedOne, it) } )
        elementContainer.registerFactory( MyNamedTwo, { objectFactory.newInstance(MyNamedTwo, it) } )
    }

    void elements(Action<PolymorphicDomainObjectContainer<MyNamed>> action) {
        action.execute( elementContainer );
    }

    PolymorphicDomainObjectContainer<MyNamed> getElements() {
        return elementContainer
    }
}

extensions.create( 'myExt', MyExt )

myExt {
    elements {
        a(MyNamedOne) {
            zero = 'hello'
            one = 'world'
        }
        b(MyNamedTwo) {
            zero = 'or not'
        }
        c(MyNamedOne) {
            zero = 'bye'
            one = 'for now'
        }
        d {
            zero = 'so long'
        }
    }

}

task qwe {
    doLast {
        myExt.elements.withType( MyNamed ) {
            println "${it.name}: ${it.zero.get()}"
        }
        myExt.elements.withType( MyNamedOne ) {
            println "[One] ${it.name}: ${it.zero.get()} ${it.one.get()}"
        }
        myExt.elements.withType( MyNamedTwo ) {
            println "[Two] ${it.name} ${it.zero.get()}"
        }
    }
}

Result of qwe task:

> Task :qwe
a: hello
b: or not
c: bye
d: so long
[One] a: hello world
[One] c: bye for now
[Two] b or not

Thanks @Chris_Dore

That’s roughly what I tried initially, as I thought that is how it is supposed to work. However, this gives me errors about being unable to find a method with the given domain object name (a, b, … in your example):

org.gradle.api.GradleScriptException: A problem occurred evaluating root project 'simple'.
	...
	
Caused by: org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method orm() for arguments [com.github.sebersole.gradle.quarkus.dsl.StandardExtensionSpec@3e697228, build_efel1wrxisx65esciz2niim8a$_run_closure4$_closure7$_closure8@36037961] on ExtensionSpec container of type org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer

“orm” would be like your “a”. Maybe something to do with your “code” being groovy and mine being Java?

Just to rule out any additional issues, have you tried it after moving away from the name “extensions”? In case there’s some remaining collision with ExtensionAware.

Do your extension classes have a getName() method? Can you show more about how you implemented the registered factories? Instead, could you create a simple project on github/etc that reproduces the issue? I’m happy to give it some debug time, but I’m not certain how to fill in the blanks from your given example.

EDIT: Also, what Gradle version are you using?

Thanks again Chris,

Actually I gave up using the term extensions for this exact reason:

quarkus {
    extensionSpecs {
        orm(HibernateOrmExtensionSpec) {
            databaseFamily = 'derby'
            ...
        }
        ...
    }
}

This is the build script that gave that error.

Yes the ExtensionSpec objects implement Gradle’s Named contract.

Another consideration that I just considered… could this be related to TestKit? The error happens in one of my TestKit tests for the plugin. I know the official tagline is that TestKit operates just like a normal run, but that has been anything but my experience.

As far as Gradle version, I use 6.1 via wrapper.

I’ll work on committing and pushing the changes as a branch to the plugin GitHub repo for you to see.

You can run the TestKit tests (gradlew test) to see the error. Caused by this line in the build script: https://github.com/sebersole/quarkus-gradle-plugin-poc/blob/properties_and_polymorphicExtensions/quarkus-gradle-poc-plugin/src/test/projects/simple/build.gradle#L50

The factories get registered in com.github.sebersole.gradle.quarkus.dsl.ExtensionSpecContainer#ExtensionSpecContainer

For sure it does not like the “HibernateOrmExtensionSpec” part. I changed that to orm("HibernateOrmExtensionSpec") {...} and it at least now recognizes the correct spec type:

* What went wrong:
A problem occurred evaluating root project 'simple'.
> Could not find method orm() for arguments [com.github.sebersole.gradle.quarkus.dsl.HibernateOrmExtensionSpec, build_efel1wrxisx65esciz2niim8a$_run_closure4$_closure7$_closure8@5fbff902] on ExtensionSpec container of type org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer.

It is now an instance of HibernateOrmExtensionSpec where the previous error showed StandardExtensionSpec. So it is at least getting the ExtensionSpec type right.

But its not recognizing “orm” as the extension name. I am probably just missing something obvious, but looking at PolymorphicDomainObjectContainer it does not have any create methods for name+type+closure or name+type+action.

I broke this down a little bit more.

I started a new project https://github.com/sebersole/gradle-polymorphic-containers which imported your script work directly as a TestKit script. That part worked fine.

Where I started getting into trouble was breaking the DSL pieces out of the TestKit build script into the plugin’s src/main/java.

At least part of the problem is that I guess I do not know the proper syntax for referring to a packaged Java class from a Groovy build script - extensions.create( 'quarkus', QuarkusSpec ) no longer works once those classes have been broken out.

I’ve tried a slew of options:

extensions.create( 'quarkus', QuarkusSpec )
extensions.create( 'quarkus', QuarkusSpec.class )

extensions.create( 'quarkus', my.package.QuarkusSpec )
extensions.create( 'quarkus', my.package.QuarkusSpec.class )

extensions.create( 'quarkus', 'QuarkusSpec' )
extensions.create( 'quarkus', 'my.package.QuarkusSpec' )

even

import my.package.QuarkusSpec
extensions.create( 'quarkus', QuarkusSpec )

None of which work

So it is clear that at least part of the problem is the classes not being available to the script - aka not added to the script’s (buildscript) classpath. Even though TestKit says it adds the plugin to the script’s classpath, my experience is that it does not. Another one of those limitations of TestKit.

So I started thinking of ways I could get the classes added to the script’s classpath. So I went the buildSrc route. That “works” - the script can now at least see the classes. However, other things break down, mainly the @Inject stuff you had:

    @javax.inject.Inject
    public QuarkusSpec(ObjectFactory objectFactory) {
        ...
    }

    // I tried both:
    extensions.create( 'quarkus', QuarkusSpec, project.objects )
    extensions.create( 'quarkus', QuarkusSpec )

This fails with

> No service of type ObjectFactory available in DefaultServiceRegistry.

Though why I get that failure when I explicitly pass the ObjectFactory makes no sense to me…

Anyway, I’ve pushed all of this to https://github.com/sebersole/gradle-polymorphic-containers. You can move through the commits to see the progression from your script (~verbatim) through the buildSrc split

Anyway, thanks for your earlier help @Chris_Dore

I’m again going to assert that it seems like PolymorphicDomainObjectContainer does not work the way it is designed to work in all cases. I wish I knew the boundary of these cases, but I simply do not. Clearly as soon as the named-objects are defined outside the script it starts getting dicey. Beyond that… :man_shrugging: