On 15/01/2013, at 10:27 PM, Luke Daley wrote:
> Wanted to get a thread started on ideas for how this might work.
>
> The problem generally is that our containers internalise construction, and
> offer no way to parameterise it.
>
> sourceSets {
> custom {}
> }
>
> The use has no way of influencing what kind of thing “custom” is. The
> immediate need for this kind of thing is with the publications container. The
> user needs to be able to add arbitrary maven, ivy etc. publication objects.
>
> Options:
>
> 1. Add via concrete types, with rigid constructor contract
>
> To add something of a certain type, you specify the actual type, which must
> have a 1 string arg constructor (the item name)…
>
> publications {
> custom(DefaultMavenPublication)
> }
>
> class DefaultMavenPublication implements MavenPublication {
> DefaultMavenPublication(String name) { … }
> }
>
> 2. Add via concrete types, allowing constructor params
>
> publications {
> custom(DefaultMavenPublication, arg1, arg2)
> }
>
> class DefaultMavenPublication implements MavenPublication {
> DefaultMavenPublication(String name, Object arg1, Object arg2) { … } //
> arg1 & arg2 could be typed of course
> }
>
> 3. Add via contract types, using rigid factories…
>
> publications.registerTypeFactory(new MavenPublicationFactory())
> publications {
> custom(MavenPublication) {}
> }
>
> class MavenPublicationFactory implements
> DomainObjectFactory<MavenPublication> {
> MavenPublication createDomainObject(String name) {
> instantiator.newInstance(DefaultMavenPublication, name)
> }
> }
>
> (not suggesting that as the DomainObjectFactory API, just illustrative)
>
> 4. Add via contract types, allowing construction params…
>
> publications.registerTypeFactory(new MavenPublicationFactory())
> publications {
> custom(MavenPublication) {}
> }
>
> class MavenPublicationFactory implements
> DomainObjectFactory<MavenPublication> {
> MavenPublication createDomainObject(String name, Object...
> constructionArgs) {
> // constructionArgs doesn't necessarily need to be passed to
> the constructor
> instantiator.newInstance(DefaultMavenPublication, name,
> *constructionArgs)
> }
> }
>
> 5. Add via contract types, using typed builders to parameterise
>
> publications.registerTypeBuilder(MavenPublicationBuilder)
> publications {
> custom(MavenPublication, «configure the builder by map») {
> someMethodOnMavenPublication()
> }
> }
>
> publications.registerTypeBuilder(MavenPublicationBuilder)
> publications {
> custom MavenPublication), {
> « configure builder by closure »
> }, {
> someMethodOnMavenPublication()
> }
> }
> class MavenPublicationBuilder implements
> DomainObjectBuilder<MavenPublication> {
>
> MavenPublicationBuilder(String domainObjectName, Instantiator
> instantiator) { … }
>
> MavenPublication build() {
> instantiator.newInstance(DefaultMavenPublication, name)
> }
> }
>
>
> I think option 3 is the most desirable, if we can avoid the need to
> parameterise what gets created at construction time. 5 offers the most
> flexibility, but I can't think of a way to avoid making the DSL so cumbersome
> and to avoid the complexity.
>
> Perhaps the two aren't mutually exclusive. We could start with 3, and then
> add something like 5 later if it turns out to be necessary.
>
> 1 and 2 are non starters for me. There needs to be indirection between the
> requested and concrete type.
>
> I don't like 4 because it's so loose. There's no compile time typing and no
> good way to specify the contract.
>
>
> Given those options (feel free to propose others), I'd vote for starting with
> 3 I think.
I agree. It's actually what Daz ended up implementing, too, so that works out
well.
But I don't think it's quite right yet, from a plugin author's point of view.
There are a few things we still need to sort out, which influence how well
these options work:
1. A public way to do dependency injection into these domain objects. This to
me is a good option for avoiding construction parameters.
2. A public way to do DSL injection. Every object that ends up in these
containers needs to be DSL-aware.
3. We want the code to be as declarative as possible. When things are
statically declared we can infer things like the dependencies between things,
we can include interesting information in the DSL reference, and we can infer
which plugins provide which types of things for implicit plugin application,
and so on. When opaque factories are used, we cannot do this useful stuff.
4. We need to open up some of the domain object types for extension. For
example, we want people to be able to provide their own component
implementations, report implementations, and so on. I think the approach we've
used for tasks actually works pretty well in practise, as far as extensibility
goes (assuming we've moved the TaskInternal stuff off DefaultTask), so I would
suggest generalising this approach somewhat:
- An abstract contract type (eg Task)
- One or more public implementations (eg DefaultTask) open for
extension.
- The public implementations have a zero-args constructor and rely on
(field) dependency injection to receive the internal services they require.
- Instances must be managed by Gradle - you can't use new Thing().
- Class decoration injects whatever internal stuff is needed (eg
TaskInternal)
- When a type is not specified at construction time, or when a contract
type is specified, then an appropriate implementation type is selected.
I think generally we want using a factory to be a last resort for the plugin
author. Instead we want some way for a plugin author to say 'I provide
instances of this implementation type' and separately for a consumer (possibly
the plugin author) to say 'please give me an instance of this type' and for
Gradle to take care of the rest.
--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com