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

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

Reply via email to