On 05/02/2013, at 23:08, Adam Murdoch <[email protected]> wrote:

> 
> 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.

The task name is also the primary interface between the user and Gradle.

> * 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.

How do we require names for this now?

> * 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 }

I know it's not the point but we should be _very_ care about introducing any 
more ASTs. There use can be very confusing for users and could make IDE support 
even more difficult.

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

So items is just a factory?

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

Why would there even be a publications container? Couldn't you just query the 
items graph for all of the publications?

> // 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 { … }
> }

Off the point again, but…

If we are considering heavy DSL changes, helping IDEs understand should be high 
priority. Type tokens would go a long way (combined with DSLD).

> // 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

There are some interesting base ideas here, but as long as we need a flat 
namespace for tasks, and derive those names from items, I don't see how it 
solves the problem. 


> 
> 
> --
> 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