Read-write versus read-only extension properties defined via custom plugin

Hello

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'
       }
   }
}

Hey,

I would suggest to register your own Extension. That way you can simply have ro and rw properties as you know it from the Java world. have a look at the userguide section about declaring your own extensions in gradle: https://docs.gradle.org/current/userguide/userguide_single.html#customPluginWithConvention

cheers,
René

That makes sense.

Thanks

PS: Sometime things are so simple that one doesn’t see them.

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?

Thanks

Bump.
Anyone that can shed a light on my followup question (the reply before this one) about concurrent access of Extension objects?

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.

Hi Benjamin

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)?