Enforce extension data type in custom plugin extension

I want to enforce the type of value in a custom extension. For example, the java-library plugin exposes JavaVersion (it’s an enum):

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

How do I expose to the build.gradle file a class similar to JavaVersion in my custom extension so users can only type the right set of values?

Just create an enum MyEnum and have in your extension a Property<MyEnum>.
Did you try something like that and have a concrete problem with it?

I tried something like this in my plugin but it did not work

MyEnum.java

public enum MyEnum {
    ONE,
    TWO,
    THREE;
}
MyEnumExtension.java

public class MyEnumExtension {

    public ListProperty<MyEnum> myEnum;

    @Inject
    public MyEnumExtension(final ObjectFactory objectFactory) {
        this.myEnum = objectFactory.listProperty(MyEnum.class);
    }

    public ListProperty<MyEnum> getMyEnum() {
        return myEnum;
    }
}
MyEnumPlugin.java

public class MyEnumPlugin implements Plugin<Project> {
    @Override
    public void apply(Project target) {
        target.getExtensions().create("MyEnumExtn", MyEnumExtension.class, target.getObjects());
        target.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(Project project) {
                MyEnumExtension testExtension = project.getExtensions().getByType(MyEnumExtension.class);
                System.out.println(testExtension.getMyEnum().getOrNull());
            }
        });
    }
}

I published this plugin, added it to my build.gradle file with MyEnumExtn extension

MyEnumExtn {
    myEnum = [MyEnum.ONE, MyEnum.TWO]
}

Now my build fails with the error message below

Could not get unknown property 'MyEnum' for extension 'MyEnumExtn' of type com.me.extensions.MyEnumExtension.

Besides that afterEvaluate is of course evil, just for testing it probably is fine.

Don’t forget to import your MyEnum class in the build script.

Is it possible to make it work without importing MyEnum class to build.gradle?

The value of entension properties are available only in the afterEvaluate block. Is there a better way to do it?

Is it possible to make it work without importing MyEnum class to build.gradle?

Yes, but I recommend to just use the import, that’s just idiomatic.

You can do some hacks though, like polluting the Gradle package namespace which is auto-imported.
Or (works for Groovy DSL consumers, but I don’t think for Kotlin DSL consumers) you can add the class as extra property.
Possibly other hack-arounds you also shouldn’t use. :smiley:

The value of entension properties are available only in the afterEvaluate block. Is there a better way to do it?

Always.
Even your statement is not correct.
They might be available in afterEvaluate, but also maybe not, for example if the are changed themselves in an afterEvaluate block that is executed after yours.
The main effect of using afterEvaluate is introducing ordering constraints, timing problems, and race conditions.
That’s why it is highly discouraged to use them and why actually the Provider/Property APIs were introduced, to get rid of afterEvaluate.
With those lazy APIs, you usually wire one property to another and then evaluate them only at execution time when no configuration changes should or can happen anymore.

If you actually need the values at configuration time, there are other patterns, like not having a property, but a function in the extension that does the logic, or using some DomainObjectCollection where you can react to items being added or removed.

I am writing a plugin that needs to know the enum value from enumExtension during the configuration phase to configure further. Unfortunately, the value is not available unless I read it from within an afterEvluate block.

I read through gradle’s documentation to see if there are examples of reading an extension property value lazily but within the configuration phase. All I could see is how these properties could be used in tasks. My use case however is different and I could not find an alternative to afterEvaluate

As I said, one possibility is to not have a property, but have a function in your extension.
The consumer can then call that function with your enum as argument and you can do the necessary configuration in that method for example.

But as I said, highly depends on the concrete use-case at hand.