Hi, what is the best practice to define extension ...
# plugin-development
a
Hi, what is the best practice to define extension classes for the configuration of gradle plugins with kotlin?
A) Plain objects A structure of `open class`es with plain values (no
Property
,
ListProperty
, ...). Similar to what is described here or here. Are there any drawbacks to this approach? Should the properties be primitive types + lists only? Are there any restrictions? B) Using Properties I found this paragraph that seem to advocate for using Property, ListProperty, ... > Java bean properties. > Sometimes you may see properties implemented in the Java bean property style. That is, they do not use a PropertyT or ProviderT types but are instead implemented with concrete setter and getter methods (or corresponding conveniences in Groovy or Kotlin). This style of property definition is legacy in Gradle and is discouraged. Properties in Gradle’s core plugins that are still of this style will be migrated to managed properties in future versions. Is there a good example that showcases the use of Properties with nested data structures? C) Or maybe something completely different?
I want to pass the resulting configuration as part of the InstrumentationParameters to a
AsmClassVisitorFactory
If I use classes for the configuration with this structure:
Copy code
open class AddTraceSpec @Inject constructor(objectFactory: ObjectFactory) {
    val classes: Property<ClassMatcherSpec> = objectFactory
        .property(ClassMatcherSpec::class.java)
        .also { it.set(objectFactory.newInstance(ClassMatcherSpec::class.java)) }

    val methods: ListProperty<MethodMatcherSpec> = objectFactory.listProperty(MethodMatcherSpec::class.java)

    val trace: Property<TraceNameSpec> = objectFactory
        .property(TraceNameSpec::class.java)
        .also { it.set(objectFactory.newInstance(TraceNameSpec::class.java)) }
}
It fails during build, because it can not serialize the properties:
Copy code
> Failed to transform classes.jar (project :feature:search) to match attributes {artifactType=android-dex, asm-transformed-variant=demoDebug, com.android.build.api.attributes.AgpVersionAttr=8.2.0, com.android.build.api.attributes.BuildTypeAttr=debug, com.android.build.api.attributes.ProductFlavor:contentType=demo, com.android.build.gradle.internal.attributes.VariantAttr=demoDebug, contentType=demo, dexing-enable-desugaring=true, dexing-enable-jacoco-instrumentation=false, dexing-is-debuggable=true, dexing-min-sdk=24, org.gradle.category=library, org.gradle.jvm.environment=android, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime, org.jetbrains.kotlin.platform.type=androidJvm}.
      > Could not isolate parameters com.android.build.gradle.internal.dependency.AsmClassesTransform$Parameters_Decorated@7847bca1 of artifact transform AsmClassesTransform
         > Could not isolate value com.android.build.gradle.internal.dependency.AsmClassesTransform$Parameters_Decorated@7847bca1 of type AsmClassesTransform.Parameters
            > Could not serialize value of type AddTraceSpec
v
Never use plain primitives. Always use properties or configurable file collections or domain object collections. Only with those you can do lazy configuration and wire things together, avoiding the idiosyncrasies of the dreaded
afterEvaluate
that you should avoid wherever possible.
In most cases you do not need an open class, an interface is enough. Gradle will care about the boilerplate for you.
And imho you should always declare your extensions explicitly
ExtensionAware
, because they will be anyway due to the decoration Gradle is doing.
a
Thanks, that was already very helpful 🙇 . And surprisingly very different to the docs 😰 Just to double check, this would be the correct approach?:
Copy code
interface AddTraceSpec {
    fun getTrace(): Property<TraceNameSpec>
    fun getClasses(): Property<ClassMatcherSpec>
    fun getMethods(): ListProperty<MethodMatcherSpec>
}
interface ClassMatcherSpec {
    fun getType(): Property<TypeSpec>
    fun getSuperTypes(): ListProperty<TypeSpec>
    fun getAnnotationTypes(): ListProperty<TypeSpec>
}
interface TypeSpec {
    fun getPackageName(): Property<String>
    fun getName(): Property<String>
}
And a helper function that simplifies working with these interfaces?
Copy code
fun addTrace(traceName: String? = null, action: Action<in AddTraceSpec>? = null) {

        val traceSpec = objectFactory.newInstance(TraceNameSpec::class.java)
        val classSpec = objectFactory.newInstance(ClassMatcherSpec::class.java)

        val spec = objectFactory.newInstance(AddTraceSpec::class.java)
        spec.getTrace().set(traceSpec)
        spec.getClasses().set(classSpec)
        spec.getMethods().set(mutableListOf())

        action?.execute(spec)
        traceSpecs.add(spec)
    }
• Is there any up to date documentation about this approach? Or some sample project that is configured like this? • Is there a simpler way to let gradle initialize the different
Property
fields? Or is it best practice to provide such helper methods, like
addTrace
that ensure the relevant
Property
fields are initialized?
v
And surprisingly very different to the docs 😰
Shouldn't really be different to the docs. Maybe some are outdated. 🤷‍♂️ Or maybe some are written with keeping supporing ancient versions in mind when Gradle was less capable. 🤷‍♂️
Just to double check, this would be the correct approach?:
"Correct" is very subjective in software development. But I'd say totally no! For example, not
fun getTrace():
but
val trace:
, ... And whether you use properties for the complex types or not is more an architectural question. For example if you want to set and / or wire
ClassMatcherSpec#type
as a whole it might be good to have it as
Property
. If you just want it for nested structure, but only the innermost properties configurable / wireable, the type would directly be the subtype. In the latter case iirc you would annotate it with
@get:Nested
so that Gradle can do the boilerplate for the nested type.
a
Thanks again, that was super helpful thank you I think I now have a good setup for the configuration of my gradle plugin
👌 1