gradle / gradle

Adaptable, fast automation for all
https://gradle.org
Apache License 2.0
16.92k stars 4.73k forks source link

No Kotlin type-safe accessor for extension of element in project-extension container #28162

Open tudortimi opened 8 months ago

tudortimi commented 8 months ago

Current Behavior

I have a project extension which is a container which contains elements. These elements themselves have extensions added. There are type safe accessors added for each of the elements in that container, but there is no type safe container for the extension added to each element.

Expected Behavior

According to the Kotlin DSL primer accessors are available for:

The accessors for the elements should be covered by the first statement. The accessor for the extension on the elements should be covered by the second statement.

This is exactly what would cover, for example, the Groovy source directory sets added as extensions to the source sets by the Groovy plugin.

Context (optional)

No response

Steps to Reproduce

I add all extensions from a plugin, because they have to be added when the plugins block is evaluated.

In my plugin, I created a dummy type for the elements to store in a container:

// in plugin
interface Element extends Named, ExtensionAware {
}

I add a container to the project as an extension:

// in plugin
def containerOfElement = project.container(Element)
project.extensions.add('projectExtensionContainerOfElement', containerOfElement)

This leads to a type-safe accessor for it being generated:

// in build script
println(project.projectExtensionContainerOfElement)

Type safe accessors are also added to elements in the container:

// in plugin
def foo = project.objects.newInstance(Element, 'foo')
containerOfElement.add(foo)

// in build script
println(project.projectExtensionContainerOfElement.foo)

I also add an extension to the foo element:

// in plugin
foo.extensions.add('someStringProperty', project.objects.property(String))

According to the Kotlin DSL primer, I expected also a type-safe accessor for the property:

// in build script
println(project.projectExtensionContainerOfElement.foo.someStringProperty)

This unfortunately doesn't work. I get a compile error.

Gradle version

8.6

Build scan URL (optional)

No response

Your Environment (optional)

No response

tudortimi commented 8 months ago

Here's a zip with a reproducible example: example.zip

tudortimi commented 8 months ago

Discussed also on the Gradle forum: https://discuss.gradle.org/t/no-kotlin-type-safe-accessor-for-extension-of-element-in-project-extension-container/47740/7

ljacomet commented 8 months ago

This issue needs a decision from the team responsible for that area. They have been informed. Response time may vary.

eskatos commented 8 months ago

Thank you, we think it is a valid feature request. We put it to the backlog of the team responsible for this area. It may take a while to get processed.


This is related to

In the same vein, observing extensions of each element instance will defeat containers laziness.

Also see https://github.com/gradle/gradle/issues/9277#issuecomment-486635757

Because observing task instances will get in the way of lazy task creation, the only way to provide type-safe accessors would be an extension model where, instead of registering extensions on task instances, the extensions would be registered by task type and could be queried without realizing the tasks.

You can replace "task" with "container element" in the above quote.

tudortimi commented 8 months ago

How does this work for the Groovy plugin, when using the Kotlin DSL? The documentation shows the following (comments mine):

sourceSets {  // container added as project extension
    main {  // element in the container
        groovy {  // extension of element in container
            // ...
        }
    }
}

I had a look at the code and groovy is contributed using an extension. This is the same situation as in the code from the issue, where the accessor doesn't get added. Is there some extra magic somewhere that triggers the creation of the accessor?

eskatos commented 8 months ago

That's because source sets are handled specially. Other core plugins eagerly resolve them already so it's not a big deal. But this is not done for all containers as explained above.

https://github.com/gradle/gradle/blob/5ff44c006f45b8c702baa4073308855eaa659a19/platforms/core-configuration/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/DefaultProjectSchemaProvider.kt#L97-L100

tudortimi commented 8 months ago

Now I get what is meant with getting in the way of lazy creation.

Is this something that I could do in my own code, for my own containers? I can find ProjectSchemaProvider in the javadoc, so I guess it's not public.

eskatos commented 8 months ago

Your plugin could ship static Kotlin extensions for your containers' elements. They would require an import but should be discoverable from the IDE content assist.

tudortimi commented 8 months ago

Is there a shorthand to access the someStringProperty extension, aside from getting project.projectExtensionContainerOfElement.foo.extensions.get("someStringProperty") and having to cast? With the old convention API, there was a withConvention() method, does something similar exist also for extensions?