Could anyone point me to any article/page to read ...
# plugin-development
b
Could anyone point me to any article/page to read about
ExtensiblePolymorphicDomainObjectContainer
usage? For whatever reason it refuses to construct my custom gradle types that regular
NamedDomainObjectContainer
has no issues with (even after registering bindings)...
v
There is not much docs about it unfortunately: https://github.com/gradle/gradle/issues/20833
Do you maybe have an MCVE with your non-working state?
b
I do not, but basically most of the issues revole around it not being able to instantiate custom managed types in a same way as NamedDomainObjectContainer. So for now instead of registering bindings, I register factories that just call into ObjectFactory::newInstance
Not sure if the two containers are event expected to function in the same way
v
For me
registerBinding(Foo::class, Foo::class)
and
registerBinding(Bar::class, Bar::class)
was enough to make the container work with
Foo
and
Bar
Does your element types implement / extend
Named
?
b
They do and because of that I kept getting "Cannot have abstract property Named.getName()" errors 😄
Also it wouldn't inject my managed properties complaining about missing ObjectFactory in services
v
Is your project by any chance open source? Or can you extract the relevant part to an MCVE? Would really be much easier 🙂
b
It's basically this https://github.com/JetBrains/kotlin/pull/4853 But as I've said - I just gave up and went factories route
Appreciate you trying to help nonetheless
v
Do you maybe still have the non-working / half-working state where you tried with EPDOC and could push it to some branch? I'd have a quick look and see where the problem might be.
At least "Local History" should have it 🙂
b
Just about to push it in, apologies, I'll send you a direct link once that's done
👌 1
v
I basically have this which finally worked:
Copy code
interface Base : Named {
    val url: Property<String>
}
interface Foo : Base {
    @get:Inject
    val objects: ObjectFactory

    val children: DomainObjectSet<FooChild>

    fun child(nick: String) {
        children.add(createChild(nick))
    }

    private fun createChild(nick: String): FooChild {
        return objects.newInstance<FooChild>().also { child ->
            child.nick.set(nick)
        }
    }
}
interface FooChild {
    val nick: Property<String>
}
abstract class MyExtension {
    @get:Inject
    abstract val objects: ObjectFactory

    val baz = objects.polymorphicDomainObjectContainer(Base::class).apply {
        registerBinding(Foo::class, Foo::class)
    }
}
b
Might be an issue with me using abstract class instead then
v
I hope it shouldn't, I just prefer interface where possible to save boilerplate
No, I replaced all three interfaces to be abstract classes and made the four properties abstract and it still works fine
Yay, there are some really long file names in that code base 😞 My work-windows does not really like those too much.
And what do I call to reproduce the error with that state checked out?
b
Hah got curious, didn't you? :D
Ok, so first, you need to change the factories to type bindings here. Then you can run
Copy code
./gradlew :kotlin-gradle-plugin-integration-tests:kgpJsTests --tests "org.jetbrains.kotlin.gradle.JsIrConfigurationCacheIT.testBrowserDistribution"
Note that you'll need JDKs 6, 7, 8 and 9+ installed for the project to configure (I used sdkman and gradle toolchains detected them just fine)
Oh and feel free to go watch a movie or something, the project can take up to an hour for first time configuration :D
v
Hah got curious, didn't you? :D
I don't need to become, I always am. :-D
Ok, so first, you need to change the factories to type bindings here
Ah, I see, I misunderstood actually. I thought you didn't get it managed even with
registerFactory
and went some other route, that state being the broken one. That it works with
ObjectFactory#newInstance
but not with the binding is imho even stranger.
Note that you'll need JDKs 6, 7, 8 and 9+ installed for the project to configure (I used sdkman and gradle toolchains detected them just fine)
I happen to have these already. 🙂 6, 7, 8, 11, 16 I'm more concerned that build will eat up my disk space actually. 😄
Oh and feel free to go watch a movie or something, the project can take up to an hour for first time configuration :D
Actually, I went to sleep after opening the project in IntelliJ and after 42 minutes the sync was complete. So maybe that was the waiting time already. 🙂
Hm, interesting, can you somehow debug that code? When I set a breakpoint and either use IntelliJ "Debug", or
--debug-jvm
it is not hit.
b
I just configure that gradle tadk in ij and then run debug worked just fine
Scratch that
v
There is no gutter icon:
And my intended breakpoint is at
KotlinWebpackRule
line 38
b
Hmm, looks like ij didn't import your project properly
Try syncing again or invalidating caches?
Otherwise here's my run configuration file (just drop it in
.run
folder at the root
And I recommend breaking at KotlinWebpackRule#L64 as thats where the main work is happening (assuming it gets instantiated succesfully in the first place)
v
It does not, that's the point. At least the error I get is where my breakpoint is, when
enabled.convention(false)
calls
getEnabled()
which tries to use the object factory service which it states is not available.
b
I think I got those too, but found them misleading. Error persists even if you comment out all interactions with properties
i.e. that's not what's causing the error imo
v
I guess they are not misleading but already a first symptom of the problem. Your problem is that the classes cannot be instantiated, isn't it?
b
Correct
But the errors implied that they were instantiated and failed during execution
v
Not what I get here:
Copy code
org.gradle.api.internal.tasks.TaskDependencyResolveException: Could not determine the dependencies of task ':kotlinNpmInstall'.
	[...]
Caused by: org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':rootPackageJson'.
	[...]
Caused by: org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':app:browserProductionWebpack'.
	[...]
Caused by: org.gradle.api.reflect.ObjectInstantiationException: Could not create an instance of type org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssRule.
	[...]
Caused by: org.gradle.internal.service.UnknownServiceException: No service of type ObjectFactory available in DefaultServiceRegistry.
	at org.gradle.internal.service.DefaultServiceRegistry.get(DefaultServiceRegistry.java:291)
	at org.gradle.internal.instantiation.generator.ManagedObjectFactory.getObjectFactory(ManagedObjectFactory.java:121)
	at org.gradle.internal.instantiation.generator.ManagedObjectFactory.newInstance(ManagedObjectFactory.java:86)
	at org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssRule_Decorated.getEnabled(Unknown Source)
	at org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackRule.<init>(KotlinWebpackRule.kt:38)
	at org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssRule.<init>(KotlinWebpackCssRule.kt:26)
	at org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssRule_Decorated.<init>(Unknown Source)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at org.gradle.internal.instantiation.generator.AsmBackedClassGenerator$InvokeConstructorStrategy.newInstance(AsmBackedClassGenerator.java:1969)
	at org.gradle.internal.instantiation.generator.AbstractClassGenerator$GeneratedClassImpl$GeneratedConstructorImpl.newInstance(AbstractClassGenerator.java:468)
	at org.gradle.internal.instantiation.generator.DependencyInjectingInstantiator.doCreate(DependencyInjectingInstantiator.java:64)
	... 227 more
b
I got some of those too 😀
To be hones I've tried so many different things to get it working yesterday that I got them all mixed up already
v
That's normal 😄
b
Gradle is amazing when it works, but when you start getting reflection errors like these hair starts to fall off quick!
v
Well, I referred to software development in general. 😄 But well, it is at least much nicer to work with than when you try to extend Maven 🙂 or worse, doing so and having problems
b
Never did never will. Is it really much worse?
v
Never did much with it either thankfully. I basically always tried to avoid Maven. I used Ant / Ivy until Gradle came up. 🙂
But Maven is pretty much a big black box where you have a hard time if something goes havoc when with Gradle you can easily debug and find out. Overly simplified of course. 😄
Hm, look what I just stumbled upon: https://github.com/gradle/gradle/pull/18260
👀 1
That probably explains it, as you test against 6.9 and 7.0 iirc. And that PR made it into 7.3 RC 1
b
Oooh, gotta remember to circle back and check with 7.3 once that's out
v
We are waiting for 7.5 😉
And I just confirmed, I downgraded to 7.2 where I have used it and get
org.gradle.internal.service.UnknownServiceException: No service of type ObjectFactory available in default services.
where it works perfectly fine with 7.3.2
b
Ah 🤦‍♂️ Good to know it wasn't all just my code then 😄
v
And it is not in the constructor here, but when I actually configure the extension and try to set a property.
b
I also found it extremely annoying that adding Named interface to noarg abstract class errors complaining that getName() is not implemented where all other object factories just implement it for you
Oh, while I have your attention, do you have any good links to read up on Namer usage? How do I inject it into the object containers?
v
I also found it extremely annoying that adding Named interface to noarg abstract class errors complaining that getName() is not implemented where all other object factories just implement it for you
Maybe same root problem? Because when I made my interfaces abstract classes and the properties abstract it still worked fine.
b
Do you have Named interface actually implemented or just attached?
v
It was just like this:
Copy code
abstract class Base : Named {
    abstract val url: Property<String>
}
abstract class Foo : Base {
    @get:Inject
    abstract val objects: ObjectFactory

    abstract val children: DomainObjectSet<FooChild>

    fun child(nick: String) {
        children.add(createChild(nick))
    }

    private fun createChild(nick: String): FooChild {
        return objects.newInstance<FooChild>().also { child ->
            child.nick.set(nick)
        }
    }
}
abstract class FooChild {
    abstract val nick: Property<String>
}
abstract class MyExtension {
    @get:Inject
    abstract val objects: ObjectFactory

    val baz = objects.polymorphicDomainObjectContainer(Base::class).apply {
        registerBinding(Foo::class, Foo::class)
    }
}
b
Yeah, probably the same root cause then
v
Oh, while I have your attention, do you have any good links to read up on Namer usage? How do I inject it into the object containers?
No, but what do you mean how to inject? Iirc if you have an object that is not
Named
you can alternatively supply a
Namer
for your child type so that the name can be derived.
b
Yeah, how do I supply it exactly. namer property in all the containers is immutable after instantiation
v
Why would you want to change it after instantiation? The naming strategy should not change during the lifetime of the container.
b
Because I do not know any other way 😄 I basically need to provide my own namer, but none of the ObjectFactory functions allow me to do that. So my next though was ok, I'll set it straight after I construct the container
Is there some annotation that would allow me to attach a namer to my custom type?
v
Ah, now I know what you mean. Didn't actually use a
Namer
yet. 😞
b
Another hole in the docs, unfortunately
v
If it is possible actually
b
Should be, otherwise what's the point of the interface? 😄
Even DomainObjectContainer docs mention Namer as an alternative
v
Wouldn't be ther first class they have in the public API part accidentally 😄
b
Oh, I'll make a note to expect such things moving forward then
v
Nah, better ask for it or complain, then it can either be fixed, documented, or moved to internal. 🙂
Even DomainObjectContainer docs mention Namer as an alternative
Where? Especially as that class does not exist 😄
b
I meant `Named`DomainObjectContainer. But I'm guessing you got that already
v
Even there I don't find any mention of
Namer
besides the
getNamer
method
b
Hmm, might be confusing it with something else, but I could swear I saw multiple references to Namer in "some" docs
🤷‍♂️ 1
v
Ah, well,
Namer
must of course be public API even if you cannot supply one, unless methods like
getNamer
are moved to internal too.
At least the default object factory code does not look like you are able to somhow inject something custom.
b
Unless you construct your own container implementations, which is a colossal task!
v
... yes
Better just make your element implement
Named
or if it is a
Map
have a
name
key or actually just have a property
name
even if it does not implement
Named
👍 1
The
Named
interface is probably actually most useful if you want Gradle to just implement it for you.
b
Agreed, when it works of course (unlike in this pesky case) 😄
v
Yep, well it works, if you do not use
registerBinding
😄
It's the same with for example abstract task classes or managed properties. If you want to support older Gradle versions, you cannot have them.
b
Yep, well it works, if you do not use registerBinding 😄
Not really, I still had to implement them properly and annotate my constructor with @Inject which made my kotlin classes ugly. There really should be
@constructor:Annotation
selector so I could at least put those annotations on top of the class
v
I guess that is just a product of you being desperate to fix it and trying out various things. I'd guess as you use
ObjectFactory#newInstance
now, it should also work to just declare that you want to have
Named
implemented. Because
ObjectFactory#newInstance
should do the same that
registerBinding
does from 7.3 on
b
Testing it now!
👌 1
v
I think you should be able to just remove the
getName
override in
KotlinWebpackRule
and the constructors and it should probably just still work
... and look nicer 🙂
b
BTW, I lost count of how many times my IDE plain crashed while trying to run anything on that project... I suspect JB recent focus to cloud development might've been sparked by Kotlin repo 😄
v
Didn't see any crash 🙂
b
You're either lucky or have a beefy PC. My 8GB 16GB ram with few browser tabs openned cannot handle it
v
Well, I have IntelliJ configured with
-Xmx3g
and had 4 other small projects open while having it open. Was not super fast, but at least no crash.
b
Hmm, I'm on 1.8G. Might need to pump those numbers up
v
But yeah, I'm on 64 GiB RAM laptop. 😄
Otherwise it couldn't handle my hundres of Browsertabs 😄
b
Hmm, isn't newInstance supposed to add name to the constructor? Getting
Caused by: org.gradle.api.reflect.ObjectInstantiationException: Could not create an instance of type org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssRule.
after removing my constructors and getName implementations
There was "too many arguments" message further down too
v
Not sure whether it just adds a
getName
implementation or also changes the constructor.
Ah, one moment, I remember something
Try using
ObjectFactory#named
instead of
ObjectFactory#newInstance
b
I cannot, because then my lazy providers will not be instantiated 😄
v
?
b
i.e. managed properties
v
Are they not? 😞
b
abstract val mode: Property<String>
Says so in
named()
docs
v
Ah, yeah, also "Objects created using this method are not decorated or extensible."
😞
b
To be honest I see no pint in that named method at all 😄
v
It is for attributes
b
What attributes? It can only instantiate Named interfaces or classes that have no other abstract stuff
v
Copy code
attributes {
    attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, jvmVersion)
    attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API))
}
variant-aware resolution attribute stuff
b
Ah, makes sense. Quite niche though
v
Actually I replaced
Copy code
registerBinding(Foo::class, Foo::class)
by
Copy code
registerFactory(Foo::class.java) {
    objects.newInstance<Foo>(it)
}
and it still works fine, so the name parameter seems to get added to the constructor
b
I had exactly the same thing, yet it fails complaining about no
@Inject
annotated constructor. Does that mean that I still need to annotate my noarg constructor?
v
Ah, wait, I had the interfaces version. With abstract classes I also get the complaint about
@Inject
missing on constructor
But yes, if I add the
@Inject
to the empty constructor it works.
Another potential for a feature request