Jendrik Johannes
03/19/2026, 11:52 AMBuildModel as entry point, what should I use to implement the structure of my DSL inside it? I basically already have the "grammar" and now I wand to "implement" it with DCL in the most idiomatic/minimal way possible. But I can't seem to find the answer to basic questions like:
• Do I use new or should I inject ObjectFactory (or should I never have to instantiate anything myself anymore?)
• Do I still need Property/Provider ? And if yes where (String vs Property<String>)
• When do I need which annotation (such as @Nested and @Adding )
• Is NamedDomainObjectContainer something like a standard that should be used? Or can/should I use my own clean data structures?
• What's managed by Gradle and what do need to implement "by hand". E.g. would I do action.execute(element) in my code or is that already a sign that I am doing something "wrong", because in DCL Gradle can do these things for me.
• ...
The problem might be that I am thinking too much in traditional Gradle terms. I am coming from the metal model where I design a DSL with traditional Gradle extensions. There I always follow certain patterns to have a "block" in the DSL - like public void blockName(Action<BlockType> action) . I have difficulties to understand what should be done different when doing the same with DCL.
Sorry if this is a stupid question. 🙂René
03/19/2026, 12:20 PMPhilip W
03/19/2026, 1:42 PMPhilip W
03/19/2026, 1:43 PMPhilip W
03/19/2026, 1:44 PMPhilip W
03/19/2026, 1:44 PMPhilip W
03/19/2026, 1:44 PMPhilip W
03/19/2026, 1:45 PMPhilip W
03/19/2026, 1:46 PMPhilip W
03/19/2026, 1:48 PMPaul Merlin
03/19/2026, 2:44 PMProperty & co
3/ Use @Nested on a val instead of providing configuring functions. Try to avoid @Adding by modeling things as data.
4/ You can use NDOC but if your data can be modeled with simplier primitives, go with the simplicity
5/ I'm not sure what action or element are in your point so I'll refrain from giving a misplaced answer 🙂Jendrik Johannes
03/19/2026, 3:08 PM// Option 1
myDSL {
processes {
register("exampleProcess1") {
// some details
}
register("exampleProcess2") {
// some details
}
}
}
// Option 2
myDSL {
process("exampleProcess1") {
// some details
}
process("exampleProcess1") {
// some details
}
}
// Option 3
myDSL {
process {
name = exampleProcess1
// some details
}
process {
name = exampleProcess2
// some details
}
}
How do I model such a @Nested list for which Gradle instantiates the objects for me? I probably just miss something obvious.
But that kind of was my point 5. In traditional Gradle, it's common to implement such a register method "by hand" and then inside you would have to create an object yourself and add it to some list you maintain yourself.
How can I model it that multiple process / register blocks are allowed which then add to a list (property)?Philip W
03/19/2026, 3:10 PMmyDSL {
processes { // @get:Nested val processes: NDOC<Process>
process("myName") {} // <- generated by Gradle
}
}Philip W
03/19/2026, 3:11 PM@get:Nested val processes: NDOC<Process> in the DefinitionPhilip W
03/19/2026, 3:12 PMPhilip W
03/19/2026, 3:13 PMJendrik Johannes
03/19/2026, 3:16 PMJendrik Johannes
03/19/2026, 3:21 PMprocesses {
process("myName") {}
// can I somehow have more here? Like:
specialProcess("otherName) { }
}Jendrik Johannes
03/19/2026, 3:32 PMPhilip W
03/19/2026, 3:33 PMPhilip W
03/19/2026, 3:35 PMJendrik Johannes
03/19/2026, 3:36 PMPhilip W
03/19/2026, 3:37 PMJendrik Johannes
03/19/2026, 3:38 PMThat's called PolymorphicDomainObjectContainer and not yet supported in DCL.Is there a discussion (issue?) on that as well? Would be interested to follow. I think a feature like that would be the right solution for what I have in mind.
Philip W
03/19/2026, 3:41 PMJendrik Johannes
03/19/2026, 4:01 PMPaul Merlin
03/19/2026, 4:33 PMListProperty<T> and expose polymorphic "Factory functions" to get something like:
myDsl {
processes = listOf(
process("myName", 23, "foo"),
specialProcess("otherName", 42)
)
}
There is currently no configuration block possible for elements so it doesn't work for all the cases supported by NDOC but it has the merits of simplicity.Paul Merlin
03/19/2026, 4:34 PM+=Jendrik Johannes
05/04/2026, 8:21 AMexpose polymorphic "Factory functions"But such functions, I would need to implement with ObjectFactory right? I can't just define them in interfaces, correct?
Jendrik Johannes
05/04/2026, 8:22 AMpopulations {
population {
source = DIRECTORY
from = "work/acomponent/src"
}
population {
source = OUTPUT
from = "GENERATE"
}
population {
source = INPUT
from = "SHARED_SOURCES"
}
}
Right now, I only have it working like this:
populations {
population("") { // "" is not used/needed
source = DIRECTORY
from = "work/acomponent/src"
}
population("") {
source = OUTPUT
from = "GENERATE"
}
population("") {
source = INPUT
from = "SHARED_SOURCES"
}
}Philip W
05/04/2026, 8:25 AMJendrik Johannes
05/05/2026, 11:06 AMpublic interface Process extends Named {
@Nested
NamedDomainObjectContainer<Population> getPopulations();
ListProperty<String> getTools();
MapProperty<String, String> getOptions();
}
What would I have to change in this interface and which annotations do I need to use? How do I get access to ObjectFactory? I tried a few different things but didn't get it working. Probably thinking in the wrong classic Gradle direction again.Javi
05/09/2026, 1:41 PMmapVersions {
mapVersion { // without qualifier, matches all versions
}
mapVersion("kotlin") { // Extend SemverDefinition, matches only versions with "kotlin" qualifier, priority over the previous one if matches
metadata = gradleProperty("kotlinVersion")
// or metadata = environmentVariable("KOTLIN_VERSION")
// or metadata = "1.5.0" // hardcoded value
// or metadata = property("kotlinVersion") // gradle property > environment variable
conditions {
condition(Condition.MetadataIsPresent) // condition enum
condition(Condition.RequestedTagPrefixMatches())
}
snapshotRules {
rule("kotlin-dev") { // if matches, the version is considered a snapshot
contains("kotlin-dev") // multiple ways to create this rule, from simple string match to regex
contains("another-string")
matches {
pattern("(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)-dev-(0|[1-9]\\d*)")
pattern("another-regex")
}
}
}
}
}Philip W
05/09/2026, 3:05 PMJendrik Johannes
05/22/2026, 6:24 AMouterType {
population { // adds first instance
source = DIRECTORY
filter { includes = listOf("src/*.xyz") }
}
population { // adds second instance
source = OUTPUT
filter { includes = listOf("**/*.o") }
}
}
Because a "Population" in this example is a complex object where users should be able to configure different details in a readable structure with named properties and subblocks, I cannot put it into a "flat" factory method like population(.., .., ..) . I want a { ... } block, with nested { ... } blocks.
I tried these in OuterType (see also previous messages):
• NamedDomainObjectContainer<Population> getPopulations() - requires each entry to have a name defined by the user, which I don't want/need. It also adds the additional populations { ... } , which I could accept, but which is rather redundant in my case.
• Population getPopulations() gives me the syntax I want, but the block always configures the same instance. I tried doing something with @Adding , but that does not seem to have an effect here.
• I tried turning the type into an abstract class, injecting object factory and defining a method "old style" like this: public void population(Action<Population> action) { return objects.newInstance("...") } but that only gives me an error I cannot make sense of.
Some more pointers here would be very much appreciated. 🙏Paul Merlin
06/17/2026, 11:31 AMpopulation("name") {}.
If each "population" doesn't have a name you are looking at a simple List<Population> and a factory function for your complex object. DSL might not be as nice but it would be explicit:
populations = listOf(
population(DIRECTORY, 23)
population(OUTPUT, 23)
)
But these can't have further configuration blocks.
We don't have support for "lists of complex objects requiring nested configuration blocks" yet. Good candidate for a feature request.