I've been thinking about ways to improve Gradle's "configuration DSL" - the
way domain objects (projects, tasks, etc.) get configured with closures. The
current approach is similar to Groovy's Object.with {} method. It maps
method/property calls inside the closure directly to method/property calls
on the underlying object. What I like about this approach is that it is very
simple and predictable. As soon as you know the API, you know the DSL. On
the other hand, the approach also has some drawbacks:
- Using the DSL doesn't give much over using the underlying object's API
directly.
- In order to provide a rich DSL, one has to inflate the API with
getters/setters, methods accepting single arguments and/or varargs, methods
with varying parameter types, etc.
- As a result of inflating the API, code duplication and inconsistencies
will creep in (property vs. method syntax, different coercions supported in
different places, etc.). And the larger the API grows, the more difficult it
will be to use directly, for example when implementing a plugin.
I'm proposing a "smarter" (excuse the word) configuration DSL. Such a DSL
could allow this domain object:
class Foo {
String bar
List<Integer> moos
Map vars
Dependency dep
}
...to be configured as follows:
foo {
bar "some string"
moo 5
moo "6"
moos 7, 8, 9
vars one: 1, two: 2
dep "junit:junit:4.8"
dep new Dependency("junit", "junit", "4.8")
}
This example tries to give an idea of how:
- a small and focused convention-based (bean-style) API could "generate" a
rich DSL
- a method-based DSL is less noisy and feels more declarative than a
property-based DSL (no '=')
- a method-based DSL can leverage syntactic features such as varargs and
named parameters
- a smart DSL could accept arguments not accepted by the underlying API
The last point is particularly important. The idea is that the DSL leverages
a central service to automatically coerce arguments to the types expected by
the API. This keeps the API small and guarantees that the same coercions are
available everywhere, with zero effort on behalf of the API author. Plugins
could potentially extend the set of known coercions.
Of course the story doesn't end here. A smart configuration DSL could also:
- allow arguments to be lazy by accepting a closure
- free APIs from Groovy-specific types and conventions (for example by
coercing closures to Gradle's Actions)
- automatically issue a deprecation warning if a method marked with
@Deprecated is called
- etc.
I've already implemented DSLs similar to the one outlined here with great
success. The only clear downside that I can see is that taking Gradle into
this direction would likely constitute a major breaking change. However, the
potential benefits convinced me to bring up the topic anyway (and before
1.0). What do you think? Looking forward to hearing your opinions.
--
Peter Niederwieser
Developer, Gradle
http://www.gradle.org
Trainer & Consultant, Gradle Inc.
http://www.gradle.biz
Creator, Spock Framework
http://spockframework.org
--
View this message in context:
http://gradle.1045684.n5.nabble.com/Ideas-on-improving-Gradle-s-configuration-DSL-tp3388556p3388556.html
Sent from the gradle-dev mailing list archive at Nabble.com.
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email