On 06/02/2013, at 2:27 AM, Daz DeBoer wrote:

> On 4 February 2013 15:50, Adam Murdoch <[email protected]> wrote:
> 
> On 05/02/2013, at 5:12 AM, Daz DeBoer wrote:
> 
>> On 4 February 2013 00:07, Adam Murdoch <[email protected]> wrote:
>>> Hi,
>>> 
>>> So, we're planning to have a bunch of 'jvm binaries' that can be built from
>>> various language source sets and other things. There will be a few different
>>> types of binaries, such as class directory binaries and jar binaries,
>>> possibly some others.
>>> 
>>> Something we need to sort out is how to structure the DSL for these
>>> executable things. The current plan is to have a single container that owns
>>> all of these jvm binaries, so you might declare something like this:
>>> 
>>> jvm {
>>>    binaries {
>>>        mainClasses(ClassesDirectoryBinary) {
>>>            … some inputs and other configuration ...
>>>        }
>>>        mainJar(JarBinary) {
>>>            … some inputs and other configuration …
>>>        }
>>>    }
>>> }
>>> 
>>> There might be a similar container for native binaries:
>>> 
>>> native {
>>>    binaries {
>>>        windowsX86DebugShared(SharedLibraryBinary) {
>>>            … some inputs and other configuration …
>>>        }
>>>        windowsX86DebugStatic(StaticLibraryBinary) {
>>>            ...
>>>        }
>>>        windowsX86DebugExe(ExecutableBinary) {
>>>            …
>>>        }
>>>    }
>>> }
>>> 
>>> Some questions:
>>> 
>>> * Is using a flat name the best way to identify these things? Once you add a
>>> few dimensions, the names start to get awkward. This is certainly can be the
>>> case for native binaries, and can also be the case for jvm binaries. For
>>> example, I might have (feature, binary type, groovy version, jvm version) as
>>> relevant dimensions for a Groovy library that targets multiple groovy
>>> versions and jvm versions.
>> 
>> Are the names of these things important at all? Or in general are we
>> just forcing users to come up with a name that adds little value?
> 
> I think it varies for different types of things. For some things, a name is a 
> natural way of identifying the thing. For other things (most things?) it 
> makes more sense to identify a thing by its type and some attributes about 
> the thing.
> 
> The complication is that the set of attributes that identify a thing vary 
> based on what I'm building. For example:
> 
> * If I have a single publication, then I want to refer to it as 'the 
> publication'. The other stuff (type, groupId, artefactId, version) are just 
> attributes of the publication.
> * If I publish 2 maven modules, then I want to refer to them as the 'api 
> publication' and the 'impl publication', say.
> * If I build debug and release variants of my windows executable, then I want 
> to refer to them as the 'debug executable' and the 'release executable'. All 
> the other stuff (windows, amd64, multi-threaded, visual-c++ compiler, 
> optimisation-level) are just attributes of the publication.
> * If I build debug and release variants on windows and linux for x86 and 
> amd64, then I want to refer to them using a tuple such as (windows, amd64, 
> release).
> 
> That is, a thing often just has a bunch of attributes, any of which could be 
> used to identify it, and it's how the thing is different to the others that 
> is useful for identifying it.
> 
> Right, so it "name" just another one of those ways of identifying? Sometimes 
> I want to give something a meaningful name, sometimes forcing me to come up 
> with a name is a pain in the ass.
>  
> One nice aspect of ditching the name is that a thing can more naturally live 
> in different containers and be grouped in different ways. Which would mean 
> that some of these questions about how things are grouped become less 
> important - just group them whichever way you like.
> 
> 
>> How
>> often does a user need to differentiate between them by name?
> 
> There are a few main reasons, I think:
> 
> 1. To configure something that some other logic (a plugin, say) has already 
> defined.
> 2. To configure the tasks that do work with the thing (compile it, generate 
> the pom.xml for it, publish it).
> 3. To find the thing to use it as input for some other thing.
> 4. To refer to the thing before the 'identifying' attributes have been 
> calculated. For example, to refer to a publication before the version has 
> been calculated.
> 
> None this necessarily requires a name - this is just what the name is used 
> for at the moment.
> 
> And I'm not sure any of these are the 'standard' case either. Again I refer 
> to repositories: imagine that we used the new "name(Type)" syntax. Users 
> would be forced to come up with a name for each of their repositories, which 
> would likely not be used elsewhere. Instead, we give the ability to supply a 
> name _if_ they want to refer to the repository elsewhere.
> 
> One thing that concerns me about the "name(Type) {}" syntax is that it's 
> possibly trickier to document, and trickier for users to grok what's going 
> on. In some cases it might make for a cleaner DSL, but I'm not certain it's 
> worth the cost.
>> We could consider a DSL similar to the repositories syntax:
>> 
>> jvm {
>>    binaries {
>>        classes {
>>            name "main" // optional
>>            … some inputs and other configuration ...
>>        }
>>        jar {
>>            ... we generate a sensible name ...
>>            … some inputs and other configuration …
>>        }
>>    }
>> }
>> 
>> It's possible that we treat this as a standard pattern, whereby a
>> NamedDomainObjectContainer could support both with some sort of DSL
>> magic:
>> 
>> container {
>>      name(Type) {}
>>      subtype { // generated name }
>> }
>> 
>> Or maybe get rid of the 'name' method altogether, and go with:
>> 
>> // In all cases the added element must provide a unique name, which
>> may or may not be configured explicitly.
>> container {
>>       generalType(SubType) {} // eg 'publication' for 'publications'
>> container, or 'dependency' for 'dependencies' container.
>>       subType { } // eg 'ivy' for 'publications' or 'project' for
>> 'dependencies'
>> }
> 
> These are both interesting options for defining things. One question is how 
> do I get something out again, to either configure it or use it?
> 
> There would be options:
> container.findOne({attrib == "value"})
> container.findOne(attrib1: "value", attrib2: "value")
> container['name']
> container.name
> 
> Note that I'm not suggesting doing away with "name" altogether, but instead 
> making it optional.

It might be interesting to push this further, and make name a decoration of 
some kind. We've already discussed here a few cases where sometimes name is 
relevant and sometimes its not. This isn't a function of the type of thing, but 
it is instead a function of how the thing is used. Here are some other cases:

* Sometimes a piece of code is used as a task and sometimes as an action. A 
task is really just an action with a name. The name allows us to do some useful 
stuff with the piece of code (e.g. track its history, declare dependencies and 
so on), but sometimes we don't care about this useful stuff.

* When using, say, a JavaSourceSet as an input, we don't care about the name of 
the source set. We just care that it can describe some source files and compile 
dependencies. If we keep name off JavaSourceSet, we allow other interesting 
implementations that can be used as input (but not necessarily output) without 
forcing each one to have an arbitrary name.

* Coming from the other direction: Some of our domain objects are defined using 
attributes other than a name. For example, dependencies are defined using 
(group, module, version). However, these are treated as the identifier of the 
dependency and cannot be changed, even though its quite ok that these are 
changed, up to the point that they are consumed.  In other words, they're just 
attributes of the dependency. Having a consistent way to define domain objects 
in terms of their attributes, and making identity a decoration, would mean 
dependencies and publish artefacts can be defined and used in the same way as 
everything else.

Putting together a few ideas from this thread (this DSL isn't quite right, but 
should give the idea):

// defines a NativeExecutable, with a generated name. With some AST magic the 
name might be 'someNativeBinary'
def someNativeBinary = items.nativeExecutable { os 'windows'; architecture 
'amd64'; debug: true }

// defines an IvyPublication, with a provided name
def myPublication = items.ivyPublication { name 'main'; organisation: 'my-org'; 
module: 'my-module' }

// do some things with the publication
myPublication.revision = '1.2'
publishing.publications << myPublication

// creates a CompositeSourceSet with name `main` and implicitly adds it to the 
`sources` container
source {
    main { … } 
}

// which is the same as
source.add(items.compositeSourceSet(name: 'main', { … }))

// creates an IvyRepository with generated name and implicitly adds it to the 
`repositories` container
repositories {
    ivy { … }
}

// finds all Ivy repositories, regardless of their purpose and does something 
with them
items.withType.ivy { credentials.userName 'my-user'; credentials.password 
'my-password }

// finds all Ivy repositories used for publishing and does something with them
publishing.repositories.withType.ivy { … }

// finds all dependency declarations on junit
def junitDependencies = items.withType.dependency(group: 'junit', module: 
'junit')

// add all runtime dependencies on a group to another configuration
def deps = configurations.runtime.allDependencies(group: 'my-group')
configurations.otherConfig << deps

// specify a version for all dependencies on junit
items.withType.dependency(group: 'junit', module: 'junit) { version '4.11' }

// probably some way to define default values to be applied before the config 
closure is executed
// probably some way to listen for the creation of objects


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

Reply via email to