On 6/03/10 2:15 AM, John Murph wrote:
I'm posting this for two reasons. (1) an FYI for users of Gradle, and
(2) to see if anyone has any improvements to suggest.
Some time ago Steve Appling added support for doing a depend (Ant
<depend> task) prior to a compile. We have since found that this
solution does not work for cases that involve cross module
dependencies. That change would be used like:
subprojects {
compileJava.options.depend()
}
That option causes the ant <depend> optional task to be called prior
to the <javac> task, using the srcDirs and destDir of the project that
is about to be compiled. However, this is not sufficient. Assume you
have a project A that depends on a project B, and project A defines a
class that implements an interface defined in project B. If the
interface in project B is changed, the option described above does not
catch the problem. This is because the depend is run on project B
which finds that the interface changed, but does not see the
implementation and so does nothing. Project B then successfully
compiles. Then the depend is run on project A which does not see that
the interface changed (it's not defined in A) and so does nothing.
Project B then successfully compiles when it should not have (because
the implementation class is not recompiled).
A similar thing happens when you mix languages in a project.
I think the long term solution is to extract and manage the dependencies
ourselves, from the source - and across languages, too. How we actually
do that doesn't matter too much at this stage.
Right now, a really simple option would be for the dependency analysis
to delete all output classes when the classpath changes.
This analysis lead me to conclude that the depend needs to be done
holistically. All the source and classes directories for all the
projects need to be given to the depend task. So, I changed the build
script to this:
subprojects {
compileJava.dependsOn ':depends'
//compileJava.options.depend()
}
task depends << {
List sources = []
List classes = []
subprojects.each {
if (it.hasProperty('sourceSets')) {
it.sourceSets.each {
sources.addAll it.java.srcDirs
classes << it.classesDir
}
}
}
File cacheDir = new File(buildDir, 'dependency-cache')
ant.depend(srcDir: sources.join(':'), destDir: classes.join(':'),
cache: cacheDir)
}
What these changes do is cause a each subproject's compile depends on
the shared root project's depends task (so that this depends only gets
run once, immediately prior to the find compileJava task). The
depends task then takes all the source and classes directories from
the sourceSets of all subprojects and passes them to the ant <depend>
task. This mechanism works correctly for the case outlined above, but
is quite a bit slower (the depend takes about 30 seconds on our project).
Does anyone know of a better solution than a monolithic <depend> call
prior to the first compile? This solution has some not good (but not
terrible) side effects: "gradle :projectA:compile" would call the
depends task, which might delete classes from projects that are not
being compiled right now. This is not ideal, but as those classes
weren't going to work anyway (you would just get runtime errors such
as AbstractMethodError or NoSuchMethodError) isn't not terrible.
If there is not a better solution, are there any suggestions for
cleaning up the code? I don't like having to call
hasProperty('sourceSets') but not all these subprojects use the 'java'
or 'groovy' plugins in which case they don't seem to have a sourceSets
property.
--
John Murph
Automated Logic Research Team
--
Adam Murdoch
Gradle Developer
http://www.gradle.org