Tom Eyckmans wrote:
2009/3/18 Adam Murdoch <[email protected]
<mailto:[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?
- 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. How, then,
would I declare that I want something other than src/main/java as my
source directory? What if I want to use src/main/java, but want to
change the 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.
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?
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.
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?
# 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.
# 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.
# 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.
# Define how stuff should be packaged
defaultJar(JarTask, name: project.name <http://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 <http://project.name>) {
main // all of main compile + resources
}
defaultSourceJar(JarTask, name: project.name
<http://project.name>, classifier: 'sources') {
mainSources
}
defaultJavadocJar(JarTask, name: project.name
<http://project.name>, classifier: 'javadoc' ) {
javadocMain
}
defaultTestJar(JarTask, name: project.name <http://project.name>,
classifier: 'tests') {
mainTestCompile
mainTestResources
}
// or
defaultTestJar(JarTask, name:project.name <http://project.name>,
classifier: 'tests') {
mainTest // all of mainTest test compile + test resources
}
defaultTestSourceJar(JarTask, name: project.name
<http://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 <http://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 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.
Adam