On 10/05/2013, at 2:33 AM, Luke Daley <[email protected]> wrote:

> 
> On 09/05/2013, at 12:51 AM, Adam Murdoch <[email protected]> wrote:
> 
>> Hi,
>> 
>> Just revisiting the old 'how do we deal with names for domain objects' 
>> question.
>> 
>> Currently, our polymorphic container is-a NamedDomainObjectContainer. As 
>> such it forces every domain object to have a unique name. The problem with 
>> this approach is that I need to encode the type into the name.
>> 
>> For example, we are planning to use a `binaries` container to own all the 
>> binaries produced by the project. If I have a `main` Java library, then we 
>> need to add (by convention) a `mainClasses` ClassesDirectory binary and a 
>> `mainJar` Jar binary for this library. If I have a `main` Groovy library 
>> built for Groovy 1.8 and Groovy 2.0, then I need to add 
>> `mainGroovy18Classes` and `mainGroovy20Classes` and `mainGroovy18Jar` and 
>> `mainGroovy20Jar`. If I have a `main` C library built for windows and linux 
>> with 32-bit and 64-bit and static and shared and debug and release variants, 
>> well… then we need lots of names.
>> 
>> I'd like to change things so we can get rid of the type from the name. This, 
>> of course, still leaves the other dimensions packed into the name. We don't 
>> have a good solution for this yet, but I suspect one will emerge.
>> 
>> The approach I'd like to take (which isn't a new idea - can't remember who 
>> suggested it) is to make (name, type) the unique identifier for a thing.
>> 
>> The DSL for defining an object would change from this:
>> 
>> binaries {
>>    mainStaticLibrary(StaticLibraryBinary) { … }
>>    mainSharedLibrary(SharedLibraryBinary) { … }
>> }
>> 
>> To:
>> 
>> binaries {
>>    staticLibraries {
>>        main { … }
>>    }
>>    sharedLibraries {
>>        main { … }
>>    }
>> }
>> 
>> Also:
>> 
>> publications {
>>    maven { 
>>        main { … }
>>    }
>>    ivy { 
>>        main { … }
>>    }
>> }
>> 
>> 
>> To find something:
>> 
>> // look something up by name
>> def main = binaries.main  // fails if there are multiple binaries with name 
>> `main`
>> 
>> // look something up by type and name
>> def main = binaries.staticLibraries.main
>> 
>> // look something up by super type and name
>> def main = binaries.nativeLibraries.main // fails if there are multiple 
>> native libraries with name `main`
> 
> Are we automatically doing the lookup here? i.e. drop package, camel case and 
> pluralise? Or is there an explicit mapping between a type and its "dsl name"?

Possibly one and then the other as a fallback. We might also discover the 
mappings by inspecting the types (eg annotations). Or all of the above. We also 
need to know the implementation type for a given contract type and this would 
be declared in the same way. At the moment we're using explicit registration 
for this.

Whatever we come up with here would be reused for declaring tasks, meaning that 
the task types would no longer need to be visible on the script classpath. And 
this means we can get rid of the boilerplate for custom plugins, we can isolate 
plugins from each other, and we can more easily inject task types into scripts 
(eg from some other script).


> 
>> // look up all things by type
>> def libs = binaries.staticLibraries
>> 
>> And to deal with the other dimensions, you'd use the existing collection 
>> stuff:
>> 
>> // All windows static libs
>> def libs = binaries.staticLibraries.matching { it.platform.operatingSystem 
>> == operatingSystems.windows }
>> 
>> We can come up with conveniences for the other dimensions. Perhaps a 
>> map-based selector:
>> 
>> def libs = binaries.staticLibraries(platform.operatingSystem: 
>> operatingSystems.windows)
>> 
>> Thoughts? I don't think this plan quite gets it right, but it feels better 
>> than the current DSL.
> 
> I don't think it really fixes the problem. I think we want to get away from 
> the idea of an immutable name altogether. Instead, we probably want a way to 
> extract an identifier based on differentiating characteristics. This seems to 
> be how the names are used as we've discussed.

Right, this is exactly the goal. So far, no-one has come up with a good 
proposal for a DSL that solves this well.

> 
> Combining this with some of deferred configuration stuff, I think you want to 
> tell Gradle which characteristics to use to construct the identifier. This 
> also means you don't have to predict names. 
> 
> Disclaimer: I haven't really thought this through beyond reading this email.
> 
> Let's say we have the following interface:
> 
> interface Identifiable {
>       String getIdentifier()
>       void setIdentifier(String identifierPattern)
> }
> 
> An identifier pattern is a tokenisable (at runtime) string, based on 
> properties of the thing…
> 
> class HttpRepository extends AbstractIdentifiable {   
>       …
>       String getUrl()
> }
> 
> def repository = new HttpRepository(url: "http://org.com";) 
> repository.identifier = "#url"
> assert repository.identifier == "http://org.com";
> 
> The benefit here is that types can supply a default naming strategy, which 
> could be based on important characteristics. Assuming that we find some way 
> to actually "lock" objects after they've been configured with the deferring 
> stuff, we could also make the actual value immutable at this time. We could 
> defer preventing collisions until this point.
> 
> The idea is also that the identifier is only used for output and for deriving 
> names (would be good if we could avoid this in the future too). If you are 
> programmatically looking for something, you find it by looking up 
> characteristics. I don't think plugins do this often anyway. It doesn't seem 
> often to me that infrastructure code pulls objects from containers via their 
> name. You're usually agnostic to containers at this level and work directly 
> with the instances. Configurers (a.k.a users) do this all the time, but they 
> should have the knowledge required to identify the thing they want. If you 
> add in a singleFile type approach here (i.e. I'm expecting there to be one 
> thing in this collection and I want it) then I think it would work.

This is roughly what we're aiming for, I think. There are still some issues to 
sort out.

One problem is that the set of attributes that identify a thing are not 
constant for a given type:

- We want to be able to add more variant attributes in a backwards compatible 
way. For example, we want to be able to add debug vs release variants for 
native binaries. Given that binaries with different values of this attribute 
are mutually exclusive, this attribute needs to be baked into the identifier of 
the thing. If you're using the default identifier scheme, then the names for 
tasks and output directories and so on will change in a breaking way. If you're 
using your own scheme, then your build is going to break because you'll start 
generating duplicate identifiers for the new variants.

- The identifier often includes attributes that aren't relevant for building 
the thing. For example, for the 'debug' variant of the 'main' native library 
includes the identity of the 'main' native library in its identifier. Or if I 
define 'snapshot' and 'production' publications, then the fact of whether a 
publication is 'snapshot' or 'production' has to be included in its identifier.

- The identifier often does not include all the attributes that can be relevant 
for building the thing. For example, if I build all my binaries on windows and 
only ever use visual c++, then the identifier for my binaries doesn't need to 
include either the operating system or compiler version. More generally, if a 
given attribute has the same value for all objects of a given type, then that 
attribute is not relevant for identifying the objects. If there's only a single 
instance of a given type, then no attributes other than the type are relevant.

It feels like there are 2 things going on here:

- The role a thing plays in the build: This is the maven central repository, 
this is the (single) publication, this is the api binary of the java library, 
this is the test java source, this is the groovy 1.8 binary for the groovy 
library, this is the free flavour of the main Android application, this is the 
32-bit windows jni library for the main java library.
- The attributes that affect the output of the thing: target byte code level, 
groovy runtime version, c++ compiler version, target operating system, debug or 
release binary, etc.

Sometimes attributes from the second group are included in the role (or, 
perhaps, are inferred from the role), sometimes not at all. For me, a good DSL 
would use the role of a thing to identify that thing and to generate names for 
that thing - output files names, task names, and so on. It would also use the 
role of a thing to infer the roles of the things it is composed from.

You can split up the attributes of a thing into 3 groups:

1. The attributes that form the identity of the thing.
2. The attributes that affect the output of the thing.
3. The attributes whose values are derived from these other values.

All the DSL proposals so far require a multi-phase approach to configuring a 
thing. First we have to configure the attributes that form the identity, then 
we have to configure the remaining attributes from #2 (if we care), and then 
configure the attributes from #3 (if we care). The current DSL just uses name 
for the identity and this is immutable once configured.

Possibly we can't avoid this. A multi-phase approach does offer some 
interesting options. For example, we might configure the attributes of #2, and 
these become immutable. Once this has been done for all objects of a given 
type, we can infer #1, and then configure the attributes of #3.


> 
> While we are discussing the DSL we should also consider making creation 
> explicit. We have the same problem with our domain object containers as we 
> did with dynamic properties. Namely, we can't tell if the user wants to 
> create something or configure an existing thing and therefore can't detect 
> typos or tell the user that that thing doesn't actually exist.

This is a good point, and a problem with the DSL I sketched out.


--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Join us at the Gradle Summit 2013, June 13th and 14th in Santa Clara, CA: 
http://www.gradlesummit.com

Reply via email to