Lennart Jörelid
02/26/2024, 10:25 AM"Do not make it a property in the extension, but a function in the extension, then do the configuration in that function and the consumer can call that function to enable that configuration"
@VampireI am trying to distill the standard pattern for configuring the JavaPluginExtension (which, in turn would yield configuration to various Java-related tasks) using lazy configuration from my own conventions plugin. Start with parts of an extension, which has a
JavaVersion Property with the default value (convention) JavaVersion.VERSION_17 and a convenience function which can be used from an individual gradle project or another plugin:
open class EtgJavaStandardsBuildExtension(project: Project) : AbstractEtgBuildExtension(project) {
/**
* The Java version to use within this project. Will be used for both source and target compatibility.
*/
val withJavaVersion: Property<JavaVersion> = optionalProperty(JavaVersion.VERSION_17)
/**
* Convenience method to set the [JavaVersion] to use within this project.
*
* @param javaVersion The Java version to use, both for source and target compatibility.
*/
fun useJavaVersion(javaVersion: JavaVersion) = withJavaVersion.set(javaVersion)
...
}
I would now - in the conventions plugin which publishes the extension above - want to call the JavaPluginExtension's configuration methods and set its JavaVersion value with the value from my conventions plugin above. I need to do this lazily and it must permit configuration from the individual project.
1. The JavaLibraryPlugin - and therefore the JavaPlugin - is applied before the addAndConfigureExtensions method is invoked, so the JavaPluginExtension exists.
2. Currently, my implementation uses the afterEvaluate block, but from what I understand above this is a bad practise.
3. What is the best practise pattern to set the properties lazily? Ideally, no configuration should be done in individual projects, but the defaults/conventions should be used as much as possible.
open class EtgJavaStandardsPlugin : AbstractEtgPlugin() {
override fun applyPrerequisitePluginsOfType(): List<Class<out Plugin<Project>>>? =
listOf(EtgLifecyclePlugin::class.java, JavaLibraryPlugin::class.java)
override fun addAndConfigureExtensions(project: Project) {
val config = createBuildExtension<EtgJavaStandardsBuildExtension>(project, "etgJavaStandards")
// #1) Configure the JavaPluginExtension
//
project.afterEvaluate {
it.extensions.configure(JavaPluginExtension::class.java) { jpe ->
with(jpe) {
// Set the Java Version
val javaVersion = config.withJavaVersion.get()
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
// Add sources JAR to all publications
withSourcesJar()
// Add the JavaDoc JAR if asked to
if (config.addJavaDocToComponent.get()) {
withJavadocJar()
}
}
}Thomas Broyer
02/26/2024, 10:41 AMprivate val internalJavaVersion: Property<JavaVersion>
val javaVersion: Provider<JavaVersion> = internalJavaVersion // expose it, read-only, for others to possibly use
fun withJavaVersion(javaVersion: JavaVersion) {
internalJavaVersion.set(javaVersion)
javaExtension.sourceCompatibility = javaVersion
javaExtension.targetCompatibility = javaVersion
}
fun withJavadoc() {
javaExtension.withJavadocJar()
}
(and configure the default Java version and withSourcesJar() right from your plugin's configure() )Lennart Jörelid
02/26/2024, 10:43 AMLennart Jörelid
02/26/2024, 10:45 AMVampire
02/26/2024, 10:56 AMsourceCompatibility and targetCompatibility, but instead use JVM toolchains.
Those are properly leveraging `Property`s, so you can just wire your extension property to that property without the need for a function.
If you insist on setting sourceCompatibility and targetCompatibility, which are not `Property`s up to now, then yes, use a function instead.
You can inject the other plugins' extensions into your extension to configure them.
Or you can just inject the Project and get the extensions from that.
Project injection must even not be done explicitly as long as you let Gradle create your extension instance, it can automatically inject the Project instance.Vampire
02/26/2024, 10:57 AMVampire
02/26/2024, 10:57 AMLennart Jörelid
02/26/2024, 10:59 AMLennart Jörelid
02/26/2024, 11:00 AMVampire
02/26/2024, 11:13 AMProperty with convention value with imperative configuration.
The problem is with mixing lazy-logic with eager-logic.
If you have Property that you want to configure and Property in your extension (like if you would use JVM toolchains instead of *Compatibility, you can wire those `Property`s together and as long as those are evaluated as late as possible, all is fine.
If you need to configure eager things, for example plain properties like *Compatibility you have to define some point in time where you configure them.
You can of course configure them from a Property in your extension in an afterEvaluate { ... } block so that the consumer of your extension had a chance to configure that Property.
But as you noted, using afterEvaluate { ... } is bad practice in almost all cases and the main thing you add with using it is ordering problem, timing problems, and race conditions.
What happens for example if the consumer of your extension also uses afterEvaluate { ... } which is registered after you afterEvaluate { ... } and is thus also executed after yours and changes the Property in your extension in there.
You would miss that configuration and not use what the consumer configured.
These problems are why afterEvaluate is bad practice and why the Property and so on were invented.
To bridge between Property and eager config like *Compatibility you can of course use afterEvaluate, but as said will earn the same problems.
If you in this case would just set *Compatibility to your convention values in your plugin apply and then have a function in your extension that reconfigures it, you would probably achieve what you want. But as I said, how to properly do it might be different from case to case. And here I would just use JVM toolchains instead which imho are always preferable, as the decouple the Java verison used to execute Gradle from the Java version used for compilation and so on.Vampire
02/26/2024, 11:16 AMmyExtension {
config {
foo = true
}
}
you could have the "property-style" setting while configuring the eager values safelyLennart Jörelid
02/26/2024, 11:17 AMLennart Jörelid
02/26/2024, 11:17 AMVampire
02/26/2024, 11:18 AMconfig would be a function in the extension that gets Action<MyExtensionConfig> as parameter.
Then when config is called, you call the parameter and after that you can extract the information to set the other eager values.Vampire
02/26/2024, 11:18 AMLennart Jörelid
02/26/2024, 11:19 AMVampire
02/26/2024, 11:19 AMThat is simple enough with some Kotlin extension functions, so that already exists.Kotlin extension functions are always bad, unless you have a narrow set of consumers from which you know they always use Kotlin DSL. Otherwise you leave Groovy DSL users behind.
Lennart Jörelid
02/26/2024, 11:19 AMVampire
02/26/2024, 11:20 AMLennart Jörelid
02/26/2024, 11:20 AMVampire
02/26/2024, 11:20 AMVampire
02/26/2024, 11:20 AM*Compatibility to one value and then to another is no problemLennart Jörelid
02/26/2024, 11:21 AMVampire
02/26/2024, 11:21 AMProperty<Boolean>.
There for example you would need to finalize the property or might get into trouble.Vampire
02/26/2024, 11:22 AMHowever, I would - in the same manner - finalize the properties on the JavaPluginExtensions after being set in my conventions pluginWhy should you do that?
Vampire
02/26/2024, 11:22 AMLennart Jörelid
02/26/2024, 11:22 AMVampire
02/26/2024, 11:23 AMLennart Jörelid
02/26/2024, 11:23 AMLennart Jörelid
02/26/2024, 11:23 AMVampire
02/26/2024, 11:23 AMLennart Jörelid
02/26/2024, 11:24 AMLennart Jörelid
02/26/2024, 11:24 AMVampire
02/26/2024, 11:25 AMLennart Jörelid
02/26/2024, 11:25 AMVampire
02/26/2024, 11:25 AMVampire
02/26/2024, 11:25 AM*Compatibility anyway.Vampire
02/26/2024, 11:26 AMVampire
02/26/2024, 11:26 AMLennart Jörelid
02/26/2024, 11:26 AMVampire
02/26/2024, 11:26 AMProvider enabled and are preferable anyway, so there you could finalize the values you setLennart Jörelid
02/26/2024, 11:27 AMVampire
02/26/2024, 11:27 AMLennart Jörelid
02/26/2024, 11:27 AMVampire
02/26/2024, 11:28 AMVampire
02/26/2024, 11:28 AM