I am writing what is effectively a “convention” plugin around Google’s protobuf plugin. My plugin’s job is to apply the protobuf plugin and configure its sources in a manner specific to our source tree. The details aren’t relevant except that I want to add a boolean property to my plugin’s extension, and change the application/configuration of the protobuf plugin depending on the boolean’s value.
Something like this:
interface MyProtoExtension {
val nano: Property<Boolean>
}
If nano is true, I need to configure the protobuf plugin for javanano-protos. If its false, I need to configure it for javalite-protos.
However, in my plugin, if I access the boolean properties value, I always get false. When am I allowed to read the value?
class MyProtoPlugin : Plugin<Project> {
override fun apply(project: Project) = project.run {
val myExtension = extensions.create("my_proto", MyProtoExtension::class.java)
.apply {
nano.convention(false)
}
plugins.apply(ProtobufPlugin::class.java)
val pbufExtension = project.extensions.getByType(ProtobufExtension::class.java)
if (myExtension.nano.get()) { // <=== Always False
// configure for nano
} else {
// configure for lite
}
}
I’d be perfectly happy making this non-lazy as well, but changing from val nano: Property<Boolean> to var nano: Boolean seems to have no effect.
I found this earlier discussion, but it didn’t seem to actually answer the question:
I do not agree that the question was not answered there, the recommendations are still the same as in my answer over there.
That is, if the question is “how to properly solve it”.
If the question is “when can I read the value safely”, then the answer is, you cannot at configuration time, only at execution time. At configuration time you never know whether the value might be modified further. Unless of course you force the value to read-only after you have read it, but that is not really good practice.
I’d be perfectly happy making this non-lazy as well, but changing from val nano: Property<Boolean> to var nano: Boolean seems to have no effect.
Because this does not make it lazier. The Property<Boolean> is only lazy if you also treat it lazy, that is only read it as late as possible, optimally at execution time where configuration should not be changed anymore. If you have a Property<Boolean> and call get() on it this is just as eager as having a boolean and reading it.
Thank you for your reply. I think this answers my question: “you cannot [read the value] at configuration time, only at execution time.” In other words, what I need to do is impossible.
I need to use the extension’s options to change configuration.
I suppose the alternative is to write, nearly identical plugins, one of which is configured differently than the other:
my_proto { ... }
and my_proto_nano { ... }
This feels less optimal, but I will go with it unless an alternative can be proposed.
DO NOT configure the tasks outside of the generateProtoTasks block, because there are subtle timing constraints on when the tasks should be configured.
I am using there generateProtoTasks block, but it has not helped.
Yes, separate plugins are one option.
You don’t have to make them nearly identical, you can make for example a third plugin that is applied from both for the common things, or have a common base class for the two plugins with the common logic.
Or as also recommended, you could have a function in your extension instead of a property and do the respective configuration in that function, something like fun useNano()or fun useNano(useNano: Boolean). If you do it like that, make sure that the configuration changes work properly if you call it multiple times with different argument or ensure that it fails if it gets called a second time with different value.
If you would use afterEvaluate to delay the reading of the property to a time when your consumer had a chance to set the value, this will work, but only for example if your consumer does not himself use afterEvaluate to set your property for some reason. Hence the recommendations to either use a function or separate plugins, as afterEvaluate’s main effect is to introduce ordering problems, timing problems, and race conditions. That the ProtoBuf plugin is using afterEvaluate is bad practice and should be improved. The more plugins that are using afterEvaluate, the higher the probability, that you run into strange effects that are hard to understand. Hence afterEvaluate should almost always be avoided under almost any cost if possible.