2009/3/22 Adam Murdoch <[email protected]>
>
>
> Tom Eyckmans wrote:
>
>
>
> 2009/3/18 Adam Murdoch <[email protected]>
>
>>
>>
>> Tom Eyckmans wrote:
>>
>> Hi,
>> During aSkype session on sunday (Hans & me) we thought of introducing a
>> SourceDirectory concept to improve our compile/resource and test handling.
>>
>>
>> I think this is a fantastic idea.
>>
> cool
>
>>
>>
>> some of my first ideas:
>>
>> # Defining source & resource dirs:
>>
>> sourceDir('main') {
>> dir('src/main/java')
>> }
>> resourceDir('main') {
>> dir('src/main/resources')
>> replaceTokens ( prefix : '${', suffix : '}' ) ( [
>> tokenOne: 'valueOne',
>> tokenTwo: 'valueTwo'
>> ])
>> }
>> testSourceDir('main') {
>> dir('src/test/java')
>> }
>> testResourceDir('main' {
>> dir('src/test/resources')
>> }
>>
>> a more advanced example (with jdk version specific code):
>>
>> sourceDir('main') { dir('src/main/java') }
>> sourceDir('jdk14') { dir('src/jdk14/java') }
>> sourceDir('jdk15') { dir('src/jdk15/java') }
>>
>> sourceDirGroup('jdk14'){ sourceDirs('main','jdk14') }
>> sourceDirGroup('jdk15'){ sourceDirs('main','jdk15') }
>>
>> testSourceDir('main') { dir('src/test/java') }
>> testSourceDir('jdk14') { dir('src/test-jdk14/java') }
>> testSourceDir('jdk15') { dir('src/test-jdk15/java') }
>>
>> testSourceDirGroup('jdk14') { testSourceDirs('main','jdk14') }
>> testSourceDirGroup('jdk15') { testSourceDirs('main','jdk15') }
>>
>>
>> Some comments/questions:
>>
>>
> Some more questions/comments:
>
> - how do we deal with generated source/resources?
>
I think we could use the sourceDir type for this aswell, e.g.:
mainAntlrSourceDir (AntlrSourceDir) {
dir('src/main/antlr')
}
- how do these declarations interact with the convention for source
directories? Currently, if I don't declare anything, I can put my source
files under src/main/java or src/main/groovy and it all just works. It would
be very nice if things continued to work this way.
I'm not sure if it is going to be possible to keep keep the current
convention values for source directories , because you could have multiple
sourcedir domain objects with different actions on them and that doesn't map
one-one to the convention values. But what we could do is map the convention
values on the java/groovy plugin to the mainSourceDir / mainTestSourceDir
domain objects.
How, then, would I declare that I want something other than src/main/java as
my source directory?
I think we can do the same like we do with tasks, we provide mainSourceDir
and mainTestSourceDir domain objects by default and when they are declared
in the build file they get overwritten. I wouldn't go for a reconfiguration
way because that is tons more difficult and harder to grasp.
What if I want to use src/main/java, but want to change the
includes/excludes?
When I apply the overwrite way, you would have to re-declare the
mainSourceDir:
mainSourceDir(JavaSourceDir) { // type can possibly be based on the plugins
being used
dir('src/main/java') {
// custom includes/excludes
}
}
I think we want to come up with a pattern for how default domain objects
should work. There are severa places where we add a default domain object,
such as the source dir, test source dir, jar archive and test suite. And
plenty more where we could, such as a default distribution, repository,
documentation bundle, and so on.
Absolutely.
The question is, how will we allow these default domain objects to be used
if nothing is specified, but allow the build file to declare that the
default should not be used, or that the default should be changed in some
way?
I think by making them available as project properties, and apply the task
overwrite pattern on our domain objects.
How would we deal with resources and source living in the same directory?
>
I think the solution to this would be to type the sourceDirs like we do with
artifact tasks, the type would define includes/excludes.
> production and test classes in the same directory?
> java 1.4 and java 1.5 classes in the same directory?
I think both are really nasty situations that should be avoided at all times
(compile/testCompile configuration concept is useless) but in the case that
it happens, I think we should just compile it twice and don't treat it in a
special way. Best possible scenario would be to define the
sourceDirs/testSourceDirs with includes/excludes (packaged based/ or
classname based (again abstract class issues probably when not following a
naming convention)) otherwise I think it is a lost cause and it really ought
to be separate dirs anyways :).
I think providing includes/excludes will work fine.
> How would we deal with a multi-language project, which has both java and
> groovy source, or java and c source, or whatever?
>
I think by typeing the sourceDirs we have a good handle on multi-language
projects, for groovy source the type would extend the java type and include
both java/groovy sources, I wouldn't go for multi-typed sourceDirs because
we would have to figure out a pretty difficult regexp expression merger (and
I'm not nearly smart enough for that :)).
So, something like this?
groovySourceDir('main') {
...
}
Then sourceDir should probably be called javaSourceDir.
I'd go for (repeating the name of the type is very likely to be redundant
because of the usePlugin statement):
mainSourceDir { // type is based on plugins being used
...
}
I'm thinking it would be better to go for mainSourceDirs and not
mainSourceDir (notice the s), because this would map better to the the
convention values currently in use, and because of the groovy plugin uses
both src/main/java and src/main/groovy as 'main' source directories.
>
> Why do we need source dir groups?
>
In case of the following sourceDirGroup:
sourceDirGroup('jdk14'){ sourceDirs('main','jdk14') }
You include multiple sourceDirs
- main
- jdk14
By grouping these source dirs you don't need to mention both dirs when
referencing the group.
Why don't we just use a groovy List or Set?I think by just using
mainSourceDirs (plurial) the concept of sourceDir groups is dead and doesn't
anything. I was thinking it would be usefull to link sourceDirGroups to one
or more platforms.
>
> # Define how stuff should be compiled
>
> // default by name and synhtetic
> compile('main') {
> // options
> }
> // or
> compileMain {
> // options
> }
>
> // custom compiles (naming optional)
> compile ( sourceDir('build/generated-java-sources') ) {
> // options
> }
> // or
> compile ('antlr-source-compile',
> sourceDir('build/generated-java-sources') ) {
> // options
> }
>
>
> What are these defining? compile tasks? some kind of domain object?
>
both a domain object and a task (more info in reply to the more generalized
idea)
We should choose one or the other. Either you define the domain object, and
zero or more synthetic tasks are created for it, or you define a task, which
has-a domain object of the appropriate type. I think domain object is almost
always the way to go.
The names of the domain object should be nouns, and the tasks should be
verbs. What you're defining above could be thought of as a ClassDirectory,
analogous with a SourceDirectory, and produced by compilation,
instrumentation, weaving, exploding a jar, etc.
I agree
>
>
> # Define how stuff should be tested:
>
> // default by name and synhtetic
> test('main') {
> // options
> }
> // or
> testMain {
> // options
> }
>
> // custom compiles (naming optional but required when you want to be able
> to use it from the command line)
> test (sourceDir('main'), testSourceDir('main') ) {
> dependsOn(...) ?
> // options
> }
> // with explicit name
> test ('slow-test', sourceDir('main'), testSourceDir('main') ) {
> // options
> }
>
>
> what are these defining?
>
both a domain object and a task (more info in reply to the more generalized
idea)
I think what you're defining here is a TestSuite. And a TestSuite has a
synthetic task to execute it, and possibly a task to generate the report for
it.I like this.
>
> # Define how stuff should be javadoc-ed
>
> javadoc('main') {
> // options
> }
> // or
> javadocMain() {
> // options
> }
>
> // custom javadoc
> javadoc('custom', sourceDirs('jdk14','antlr-sources') ) {
> // options
> }
>
>
> what are these defining?
>
both a domain object and a task (more info in reply to the more generalized
idea)
I don't think we should bother with a domain object for API documentation,
yet. Let's leave it until we get the more basic compilation, testing and
bundling stuff looking better. We should just stick with the Javadoc and
Groovydoc tasks.Indeed these are not really high prio thins.
>
> # Define how stuff should be packaged
>
> defaultJar(JarTask, name: project.name) {
> mainCompile { // include the result of main compile
> // additional options includes/excludes ?
> }
> mainResources // include the result of main resources
> }
> // or
> defaultJar(JarTask, name: project.name) {
> main // all of main compile + resources
> }
>
> defaultSourceJar(JarTask, name: project.name, classifier: 'sources') {
> mainSources
> }
>
> defaultJavadocJar(JarTask, name: project.name, classifier: 'javadoc' ) {
> javadocMain
> }
>
> defaultTestJar(JarTask, name: project.name, classifier: 'tests') {
> mainTestCompile
> mainTestResources
> }
> // or
> defaultTestJar(JarTask, name:project.name, classifier: 'tests') {
> mainTest // all of mainTest test compile + test resources
> }
>
> defaultTestSourceJar(JarTask, name: project.name, classifier:
> 'test-sources') {
> mainTestSources
> }
>
> // this way it is very easy to define bundles:
> gradleExternalJavadocBundle(JarTask, name: 'gradle-external-javadoc' ) {
> main {
> package('org.gradle.external.javadoc')
> }
> manifest {
> // additional manifest instructions
> }
> }
>
>
> We will need some way to include the these things with a prefix, as not
> everything ends up in the root of an archive.
>
I agree, just a tought:
defaultJavadocJar(JarTask, name: project.name, classifier: 'javadoc' ) {
docs { // if not defined in this context it is a directory
'api-docs' {
javadocMain
}
}
}
I like this.
>
>
> I think this is a very powerfull concept to use the task names to include
> the result of a compile/resources task in an artifact and I find it very
> natural but other views may differ.
>
>
> I agree. In fact, we should generalise it: you should be able to add any
> domain object which implements FileCollection (say) to an archive. Given
> that Configuration, Dependency, SourceDirectory, Archive, and all file
> producing tasks implement FileCollection, this makes it very easy to
> assemble archives.
>
I'm not sure if adding the FileCollection stuff on tasks is the best way to
go, I think tasks have a very clear responsibility that would get cluttered,
The clear responsibility of almost every task in existence is to produce a
set of files. Why should a task not declare what files it will produce? To
me, it seems quite remarkable that Gradle, as a general purpose build tool,
does not model this most basic of build concepts at all.
I think the quantum leap forward statement is not going to cut it once we do
:).
I think there should (optionally) be a has-a relationship between a Task and
the FileCollection (or collections) it produces. That FileCollection may be
a domain object, or may not be. This allows us to do some interesting things
in the intrastructure: tasks can declare dependencies on artifacts rather
than tasks (which is a more accurate model for most tasks), we can skip
tasks whose output already exists, you can request from the command-line
that an artifact be built.
Totally agree
Adam