Hi, I'm trying to programmatically assign a `proje...
# plugin-development
d
Hi, I'm trying to programmatically assign a
project.version
using a plugin I've written. Ideally, I'd like the plugin to support loading the
project.version
from the
project.rootProject.version
if one was not provided in the `project`'s build.gradle. Is there a method I can use to configure this at the correct time without using
project.afterEvaluate
? I'm unsure if the project needs to be evaluated before the version can be determined, or if there's a different way I should be looking to evaluate the version. I tried using
project.configure(project.version, { //... }
but this seems to be too early, considering the property I'd like to evaluate is still the default
convention
value.
To add more context, up to this point, I've been using
project.afterEvaluate
to evaluate if the
project.version
is
'unspecified'
, and if it is, use the
project.rootProject.version
as the
project.version
.
Also, I apologize if this isn't the correct place to ask. I just noticed this channel isn't specifically for community support. If needed I can move this thread over there.
I've seen this thread that talks about lazily loading the property using a provider. I was looking to use a
Property<Boolean>
to control whether or not the convention (using the root project's version) should be used. https://gradle-community.slack.com/archives/CA745PZHN/p1706038393743189
v
The whole Slack is about community support. #CAHSN3LDN is just the catch-all for all no specific channel exists and for people too lazy to look through the list of channels. 😄 And the "community" in its name should just strengthen the fact that you do not get support by Gradle employees, but users helping users. So your choice of channel is perfectly fine. 🙂
👍 1
But I'm not sure what you try to do is a good idea. Many things will eagerly use the version as it still is not a lazy property and changing it from your plugin might be way too late for those places to get your changed version.
I myself also do something similar in an internal plugin, but I simply require the version to be set using
gradle.properties
as then it is already set when my plugin runs and I just set my enhanced version then already.
d
Awesome, thanks for clarifying! To clarify on your
gradle.properties
approach, if I define a property in that file, and that file lives at the root of my project, will that property be included in all subprojects or in the root project?
v
All. And you do not even need to "check the property", Just using
project.version
works fine, it is backed by
version
from
gradle.properties
as long as not overwritten
👍 1
a
Often the afterEvaluate {} stuff can be replaced with a convention plugin. Coincidentally I wanted the same outcome as you, so I use a custom 'base' convention plugin https://github.com/adamko-dev/dokkatoo/blob/dbd980373e6524dfc80a41bc81159ec0c1210915/buildSrc/src/main/kotlin/buildsrc/conventions/base.gradle.kts#L14-L17 So if a subproject sets a custom
project.version
, it will override the convention (which is
version = rootProject.version
). Does that sound like it will help?
d
Yes, that helps 🙂 My only concern is that we are overwriting the
project.version
to append a qualifier, and any consumer of the plugin that sets a custom
project.version
will also need to handle appending a qualifier. Ideally we'd have the plugin do that, but that doesn't sound like it will be feasible.
v
With
version
not being lazy, you can probably not do much but using
afterEvaluate
😞
Hopefully with the big provider migration in Gradle 9 this changes
a
and any consumer of the plugin that sets a custom project.version will also need to handle appending a qualifier
do you mean, for example, that you want a project to set
version = "1.2.3"
and then what you're doing in an afterEvaluate is appending a qualifier? E.g.
afterEvaluate { version = "${version}-qualifier" }
d
That's correct
a
ahh okay, I misunderstood originally, but it makes sense now
what's interesting about Project.version is that it's not necessarily a String, it can be an object that has a
.toString()
that returns the version.
So you could try something like this, and have a custom class that will return a
.toString()
with the constructed version
👀 1
you might want to stick a check in the class to verify that after
.toString()
is called, the properties can't be modified, otherwise you'll end up with a version that could be different depending on when it's queried!
v
But still a consumer setting project version afterwards breaks it and you cannot really prevent it. That's like the requirement in my version, that consumers use
gradle.properties
to set the version
d
I guess going forward, both approaches will require
afterEvaluate
(for getting the qualifier on the custom project.version), so that's what I'll stick to. I've found the specific GitHub issue for getting
project
coordinates (including
version
and
group
) using providers here: https://github.com/gradle/gradle/issues/13672
Thank you both for the help today!
👌 1
v
Neither of the two requires
afterEvaluate
as part of the approaches is, that the consumer sticks to the set rules, that is does not set
project.version
manually, but either configure the property on the extension (could even be a field on
poject.version
instead, but then you need to cast to set it) or set it in
gradle.properties
, depending on approach.
d
Right, I think we're saying the same thing 🙂 We want to give consumers of the plugin the ability to set project.version if they absolutely want to manually specify a different versioning pattern than the standard (we hope not), while also having the qualifier appended to the end.
v
Well, they can set it, given the rules for either approach, without needing to use
afterEvaluate
Actually, the consumer could also always use
afterEvaluate
himself to set the version and thus again undo what the plugin did
One of the big problems with
afterEvaluate
d
If I wanted to control what
repositories
were used in a
project
, what would be the best time to evaluate that?
I've noticed that evaluating extensions properties inside of task configuration seems to be a good time to configure defaults for tasks.
v
If I wanted to control what
repositories
were used in a
project
, what would be the best time to evaluate that?
repositories
is a
RepositoryHandler
which is a
DomainObjectCollection
so you probably can use
.configureEach
or
.all
to check.
I've noticed that evaluating extensions properties inside of task configuration seems to be a good time to configure defaults for tasks.
Depends on what you mean by "evaluating". As I wrote in the forums, using
get()
on a property is not a good idea, as this still can easily be done too soon. Better wire properties to properties or evaluate them only at execution time. Whenever you request their value at configuration time, you void the advantages and again introduce ordering problem / timing problems / race conditions.
d
My motivation for using
get()
has been to set defaults for a wider-scope than individual properties. For example, one property we have is
enableDefaultEncoding
, which sets up encoding for any
JavaCompile
task and the
'javadoc'
task specifically. In hindsight though, maybe the better approach for this one specifically would be to set up the property as a
Property<String>
, that way it can be configured without using multiple configurations (disabling the boolean property, and then setting up their own encoding).
However for
JavaPluginExtension
defaults, we want to have
withSourcesJar()
and
withJavadocJar()
by default.
v
Easiest for that is to not use a property, but a function in the extension that does this configuration change
d
So that would be something like
withoutSourcesJar()
?
v
You can hardly disable sources jar and javadoc jar after you enabled them afair.
d
Easiest for that is to not use a property, but a function in the extension that does this configuration change
Was this referring to the default encoding or the JavaPluginExtension?
v
That was referring to
withSourcesJar()
and
withJavadocJar()
, as there is not a setting you need to change or a property you can wire, but some method you need to call.
Another option would be to have a separate convention plugin that the consumer can apply or not apply
Same for the default encodings, as long as the encoding you want to set is not
Property
-enabled (hopefully changes with the big provider migration in Gradle 9), there is not really a good point in time where you evaluate a
Property
to set it. So maybe better either have a function in the extension or a separate convention plugin for the different options.
Or actually a combination. A
my.convention.default.encoding.utf8
that sets everything to UTF-8 immediately. And a
my.convention.custom.default.encoding
that provides an extension with a function that you can call with an encoding that sets it. Or you just set it to the default and provide the function that the consumer can call to change it to something else. Probably no need for two plugins here.
👍 1
d
So instead of using
get()
, you're suggesting we instead make a function that lives inside the Extension class, that does the action for us, i.e:
Copy code
Boolean myFlagToDoSomething = true // The convention is that this is true

void disableMyFlagToDoSomething() {
    myFlagToDoSomething = false
}
And then at the time it's needed, check the value of
extension.myFlagToDoSomething
?
But if a setting in Gradle/somewhere is using the Provider interface, we should use a Property instead and set the value of that setting to the value of our Property coming from the extension:
Copy code
// Extension implementation
abstract Property<Boolean> htmlRequired

// Plugin implementation
// For example, the JacocoReportsContainer has a Property `html.required`
project.tasks.named('jacocoTestReport', JacocoReport).configure {
   reports.html.required = extension.htmlRequired
}
v
No, not setting a boolean from the function, but directly doing the action in the function.
Copy code
void produceAuxilaryJars() {
    ....withSourcesJar()
    ....withJavadocJar()
}
d
The extension class would need to know about the
project
correct?
v
Yes, you can just let Gradle inject it
👍 1
Copy code
abstract class MyExtension : ExtensionAware {
    @get:Inject
    abstract val project: Project
...
}
👍 1
d
Makes sense. Currently I'm passing in the
project.objects
into the constructor of the Extension class, so that should be a quick swap.
v
Usually you don't need the
ObjectFactory
as usually you can just leave the stuff abstract and let Gradle provide the implemenations like
Copy code
interface MyExtension : ExtensionAware {
    val myProperty: Property<String>
}
or
Copy code
abstract class MyExtension : ExtensionAware {
    abstract val myProperty: Property<String>
}
d
Do you have a Groovy example? I've tried converting your example to Groovy, using
implements ExtensionAware
and using
@Inject
on the
project
but that doesn't seem to work.
This seems to work
Copy code
Project project
MyExtension(Project project) {
        this.project = project
        ObjectFactory objectFactory = project.objects
}
And then I can use the
extensions.create
method without providing any
...constructionArguments
v
Copy code
abstract class MyExtension implements ExtensionAware {
    @Inject
    abstract Project project

    @Inject
    abstract ObjectFactory objectFactory
}
or something like that should work just fine
Or maybe in Groovy you need to use getters
Yeah, I think you need to use getters in Groovy
Copy code
abstract class MyExtension implements ExtensionAware {
    @Inject
    abstract Project getProject();

    @Inject
    abstract ObjectFactory getObjectFactory();
}
1
Long ago that I used Groovy for Gradle logic