I want to generate a xml file. The user declares t...
# plugin-development
p
I want to generate a xml file. The user declares the content via DCL, so I need Definitions classes. The user input is then mapped to BuildModels classes. These BuildModel is then mapped to Tasks inputs and nested classes, and this input is finally mapped to the parameters of my WorkerAction Parameter classes. So the user input needs to be passed to 4 layers using different classes (each layer needs multiple classes). This is really annoying to write and map them. And yes, you cannot reuse Definition classes due to Configuration Cache (or it is a bug). How do you handle it?
v
What CC problem do you have / get? I'd say as long as it is a fully-managed type or serializable there shouldn't be a problem, should there? Can you maybe show some MCVE?
p
I often get problems because the Script instance is wrongly part of CC. And type safe dependencies is not CC safe.
v
If the script instance is part of CC, that seems more like a bug on your side or on downstream side.
p
How can it be a bug on my side? But anyway, reusing the same Definition classes for all layers (DSL, BuildModel, Task, Action) looks like to a code smell to me.
v
How can it be a bug on my side?
Well, whoever captures the script reference. So probably not in the plugin, no. But if you for example then set a property to some provider that references the script instance, you get that problem. Really hard to talk about in theory, that's why I asked for an MCVE that shows the problem. 🙂
But anyway, reusing the same Definition classes for all layers (DSL, BuildModel, Task, Action) looks like to a code smell to me.
Well, if that is your stand, then the question is no question anymore, is it? 😄
p
It is not only my stand, but this code does fail at execution time:
Copy code
abstract class MyTask : DefaultTask() {
    @get:Inject
    abstract val workerExecutor: WorkerExecutor

    @TaskAction
    fun action() {
        workerExecutor.classLoaderIsolation {
        }.submit(MyWorkAction::class.java) {

        }
    }
}

abstract class MyWorkAction : WorkAction<MyWorkActionParameters> {
    override fun execute() {
    }
}

interface MyWorkActionParameters : WorkParameters {
    val myNDOC: NamedDomainObjectContainer<Foo>
}

interface Foo : Named

tasks.register("myTask", MyTask::class)
Cause: class java.util.LinkedHashSet cannot be cast to class org.gradle.api.NamedDomainObjectContainer (java.util.LinkedHashSet is in module java.base of loader 'bootstrap'; org.gradle.api.NamedDomainObjectContainer is in unnamed module of loader 'app')
And you also need to annotate all your DSL types with Input, Output and so on, but different tasks could need different inputs/PathSensitive etc.
And using a SetProperty shows this bug:
Copy code
abstract class MyTask : DefaultTask() {
    @get:Inject
    abstract val workerExecutor: WorkerExecutor

    @get:Input
    abstract val myNDOC: NamedDomainObjectContainer<Foo>

    @get:Inject
    abstract val objectFactory: ObjectFactory

    @TaskAction
    protected fun action() {
        println("Task: " + myNDOC.joinToString { it.name })

        workerExecutor.classLoaderIsolation {
        }.submit(MyWorkAction::class.java) {
            myNDOC.addAll(this@MyTask.myNDOC.map {
                objectFactory.newInstance<Foo>(it.name)
            })
        }
    }
}

abstract class MyWorkAction : WorkAction<MyWorkActionParameters> {
    override fun execute() {
        println("Action: " + parameters.myNDOC.get().joinToString { it.name })
    }
}

interface MyWorkActionParameters : WorkParameters {
    val myNDOC: SetProperty<Foo>
}

interface Foo : Named {
    @Input
    override fun getName(): String
}

tasks.register("myTask", MyTask::class) {
    myNDOC.create("asf")
}
v
Cause: class java.util.LinkedHashSet cannot be cast to class org.gradle.api.NamedDomainObjectContainer
That is neither a build script reference error, nor is it a CC error. This fails even with CC switched off. This imho is either way a Gradle bug. Either it should support
NamedDomainObjectContainer
at that place or give a clear error, instead of serializing it to a
LinkedHashSet
and then fail with
ClassCastException
on deserialization. So Gradle should either support it or throw a clear and actionable error.
And you also need to annotate all your DSL types with Input, Output and so on,
Yes, but that might not be a problem as they are just annotations for the case a usage needs them. 🤷‍♂️
but different tasks could need different inputs/PathSensitive etc.
If that is the case, then you should probably on task level not use it but on task level wire exactly what you need to properties annotated like you need it for that task.
And using a SetProperty shows this bug:
Not sure what you mean, it runs successfully. If I add an output so that fingerprinting happens, then you get an error that NDOC is not serializable, but that is expected. NDOC is not serializable, you probably wanted
@Nested
and with that it works
Copy code
Task ':myTask' is not up-to-date because:
  Input property 'myNDOC.asf1353989242$0' has been removed for task ':myTask'
  Input property 'myNDOC.asf1353989242$0.name' has been removed for task ':myTask'
  Input property 'myNDOC.asf-173265204$0' has been added for task ':myTask'
  and more...
p
> This imho is either way a Gradle bug. Yeah, I filled https://github.com/gradle/gradle/issues/38749
👌 1
Not sure what you mean, it runs successfully.
Whats your output?
v
Ah, you should have mentioned that the output is wrong, not that some failure happens 😄
p
Yeah, sorry, I was in my focus :D
v
Yeah, that the name is lost during serialize / deserialize seems strange
p
Sooo, I either get a hard ClassCast Error (even with nested NDOCs), or I need to use a SetProperty everywhere, nice 🫠
v
Yeah, found it referenced in the other issue, that's why I then knew what bug you meant 😄
It's even funnier - kind of. If you comment out the
getName
override and make the
Nested
Internal
instead, you get an NPE from Kotlin because
getName
returned
null
which violated its contract. You just seem to have weakened that somehow, or the name is not
null
but
"null"
. 😕
p
Time for lunch
v
Nah, it really is
null
which the JavaDoc of
getName
states must never happen. I really wonder now why it outputs
Action: null
instead of throwing NPE from Kotlin already.
Maybe you also found a Kotlin bug
Are you going to also report that probably Kotlin issue to JetBrains?
p
Yeah, will do
👌 1
v
Oh, great, it's only 10 years that the Kotlin bug is known 🙈
🙃 1