In software development there are no silver bullets, but I am always looking out for the next bronze one.

Friday, May 20, 2011

Fixing Gradle compileScala to not depend on compileJava

One problem encountered using the Scala plugin for Gradle is that the 'compileScala' task is dependent on the 'compileJava' task (as of Gradle 0.9.2). If a project has both Java and Scala sources and there are Java classes that depend on Scala ones, the build will fail. Consider two mutually dependent classes in a the same project:

Bar.java

public class Bar {
    public void doFoo(Foo foo) { ... }
}

Foo.scala

class Foo {
  def doBar(bar:Bar) { ... }
}

Gradle can't build this because Bar depends on Foo which hasn't been compiled yet. However, the Scala compiler can handle cross compilation with Java sources, so the solution is simply let the Scala compile occur first.

To override the compileScala task dependency and reverse the order of the tasks add the following to the 'build.gradle' file:

compileScala.taskDependencies.values = compileScala.taskDependencies.values - 'compileJava'
compileJava.dependsOn compileScala

The first line removes the task dependency on the compileJava task from the compileScala task. The second line makes the compileJava task depend on compileScala.

For the Java source to be available for cross compilation the directory needs to in a "sourceSet" for the Gradle Scala plugin. For example, if the Java class is in 'src/main/java' then the following will need to be included in build.gradle:

sourceSets {
    main {
        scala {
            srcDir 'src/main/java'
        }
    }
}

Using this arrangement a project with mutual dependencies like the example above should be buildable with Gradle.

2 comments:

Peter Niederwieser said...

I think compileScala.dependsOn(compileJava) is intentional, for the case where you just have Scala classes depending on Java classes (the common case) and don't want/need joint compilation. There is an extra Java compilation going on as part of the compileScala task that compiles just the Java sources to be joint-compiled. This happens after the Scala sources have been compiled.

You are right in that the Java sources to be joint-compiled have to be made known to the compileScala task, in one way or another. If you add "src/main/scala" to the "scala" source directory set, you should also take it away from the "java" source directory set. Otherwise you might get into troubles due to the compileJava task trying to compile them as well. This is probably what led you to revert the task dependency.

The simplest solution (which you may like or not) is to put all Java sources into src/main/scala. We use a similar solution for the Gradle project itself - we put all Java sources into src/main/groovy. We prefer to have our source code together in one place, independent of the language.

zhygr said...

Looks like this solution does not wokr for me anymore, because java compiler do not see the scala classes in its classpath.