Getting unexpected `Cannot set the value of read-only property` error when trying to write custom plugin DSL

I’m trying to write a DSL like the following:

toolSetups {
    someTool {
        argsFile = file('run/some_tool/xrun_files.f')
    }
    someOtherTool {
        argsFile = file('run/some_other_tool/xrun_files.f')
    }
}

I do this by adding an extension to the project, which is a container for ToolSetup entries:

extensions.add('toolSetups', project.container(ToolSetup))

I followed the examples in the user guide and declared the argsFile property in ToolSetup:

interface ToolSetup {
    Property<File> getArgsFile()
}

I searched around and found that this only works if ToolSetup has a name property. I reworked ToolSetup to be:

abstract class ToolSetup {
    private final String name
    
    public ToolSetup(String name) {
        this.name = name
    }

    String getName() {
        return name
    }

    abstract Property<File> getArgsFile()
}

I’m getting the following:

Cannot set the value of read-only property ‘argsFile’ for object of type ToolSetup$Inject.

I also tried explicitly calling argsFile.set(file(...)), but this gives me the same error. I also tried to explicitly create Property instances using ObjectFactory, but still got the same.

I already wrote a plugin that does something similar, where it works, but that plugin is written in Java. I’m guessing there’s some trivial mistake here related to Groovy and letting Gradle manage the implementation for me, which I’m not seeing. I’d be very grateful if someone could point it out.

Here’s the entire build.gradle file that reproduces this issue:

abstract class ToolSetup {
    private final String name
    
    public ToolSetup(String name) {
        this.name = name
    }

    String getName() {
        return name
    }

    abstract Property<File> getArgsFile()
}

extensions.add('toolSetups', project.container(ToolSetup))

toolSetups {
    someTool {
        argsFile = file('run/some_tool/xrun_files.f')
    }
    someOtherTool {
        argsFile = file('run/some_other_tool/xrun_files.f')
    }
}

Some notes:

You probably shouldn’t use Property<File> but RegularFileProperty.

You can easily fulfill the naming requirement by using

interface ToolSetup extends Named {
    RegularFileProperty getArgsFile()
}

If I try it with argsFile.set(file( as you said you did too, it works fine here.

1 Like

Cool, thanks! I’ll try it out tomorrow in the real project.

Is there a general limitation to mixing handwritten implementations, like I did for the name property and decorated properties, like for argsFile?

I changed it to this:

interface ToolSetup extends Named {
    RegularFileProperty getArgsFile()
}

extensions.add('toolSetups', project.container(ToolSetup))

toolSetups {
    someTool {
        argsFile = file('run/some_tool/xrun_files.f')
    }
    someOtherTool {
        argsFile = file('run/some_other_tool/xrun_files.f')
    }
}

I still get the same error when calling gradle properties:

Cannot set the value of read-only property ‘argsFile’ for object of type ToolSetup$Inject.

I tried both the Gradle version I was using (7.4.2) and also the latest one (7.6), but both fail.

I noticed that you mentioned using argsFile.set(...). That does work. I’m confused, though, as to why = doesn’t work. The example on developing custom plugins shows this syntax (Developing Custom Gradle Plugins):

interface GreetingPluginExtension {
    Property<String> getMessage()
    Property<String> getGreeter()
}

greeting {
    message = 'Hi'
    greeter = 'Gradle'
}

This seems like a bug: Convenience assignment operator (`=`) not working for injected implementation of Property on interface that extends `Named` · Issue #23525 · gradle/gradle · GitHub

The = operator works when Gradle injects an implementation for ToolSetup if it doesn’t extend Named.

1 Like

Is there a general limitation to mixing handwritten implementations, like I did for the name property and decorated properties, like for argsFile?

Not that I’m aware of.
But you just don’t need the boilerplate when Gradle can do it for you. :slight_smile:

This seems like a bug

Yep, looks like a bug to me too.

The bug seems to be related to project.container(). Using project.object.domainObjectContainer() doesn’t lead to the error. More details in the issue in Github issue.