We are currently developing a custom Gradle plugin that should provide common things such as properties and tasks to the projects we build - in essence, we aim at increasing “DRYness” throughout our project build scripts. On thing that we are currently trying to achieve is to define extension properties on the project object that are either read-only (ro) or read-write (rw); i.e., depending on whether a property p is ro or rw, on can or cannot overwrite p’s value in a project that applies our plugin. The question is whether this is possible and if so how.
Our current approach to define extension properties on the project object within the plugin is as follows:
class BasePlugin implements Plugin<Project>
{
@Override
void apply(final Project project)
{
project.ext {
// properties that should be read-only
organization = 'Foo'
organizationURL = 'http://foo.com'
}
}
}
A followup question: Now that I have created an Extension object, I want one of its properties to be read-only while lazily initialized on the first read, which means that it cannot be marked final. My current approach is to manually define a getter and setter and let the setter throw an exception:
class MyExtension {
def String myProp
def String getMyProp() {
if (myProp == null) {
myProp = // some expensive init work
}
myProp
}
def void setMyProp(String s) { throw new UnsupportedOperationException('myProp is read-only and cannot be set.') }
}
My question is whether an Extension object added to the project can be accessed concurrently by Gradle; i.e., do I need to synchronize initialization of myProp in the getter?
I don’t understand the use case of making a property read-only in an extension though you want to expose it to users. Why not initializing the value when before creating the extension and then pass in the value as constructor argument that sets it for a final property? Also, I’d recommend not putting complex logic into an extension. Try to externalize it.
Let me explain. The use case is a (strictly) monotone build number that can be either of three types: sequence, based on the current date, or nothing/not used. The build number should be managed by the plugin; that is, the plugin generates it based on the type set, one can read the build number value within a build script, but one cannot modify it (i.e., its immutable).
Correct me if I’m wrong, but I see only two ways to make such a thing read-only, considering that I want to use a plugin extension object: either declare the property final, or make the setter essentially a noop. Declaring it final is not possible in this case because there is the additional build number type that should be configurable. This implies that the build number must additionally be lazily initialized, which should become obvious when seeing the current approach:
class MyPluginExtension {
def String buildNumber // Lazily initialized.
def BuildNumberType buildNumberType
MyPluginExtension(Project project) {
buildNumberType = BuildNumberType.DATE // Set default.
}
def Long getBuildNumber() {
if (buildNumber == null) {
buildNumber = buildNumberType.createNextBuildNumber()
}
buildNumber
}
def void setBuildNumber(String n) { throw new UnsupportedOperationException('The property \'buildNumber\' is read-only and cannot be set.') }
}
enum BuildNumberType {
NONE {
public String createNextBuildNumber() { return '' }
},
SEQUENCE {
public String createNextBuildNumber() { /* ... */ }
},
DATE {
public String createNextBuildNumber() { /* ... */ }
}
public abstract String createNextBuildNumber();
}
I hope this better explains what I’m trying to achieve. Still bothering, any thoughts about my question regarding concurrent access of extension objects (and the need to synchronize lazy initialization in the getter)?