Possible to use build cache with Eclipse compiler for java?

Hello,

my question is, if it’s possible to get the gradle build cache to work for compile task while using the Eclipse Compile for Java?

At the moment we are using the eclipse compiler for java in our multi-project build, because it is way faster than the jdk compiler (minutes with the javac vs seconds with ecj).

We are doing this by setting the executable in the forkOptions of the JavaCompile Task:

tasks.withType(JavaCompile) {
        doFirst {
            def compilerArgs = []
            compilerArgs += '-warn:none'
           
            def javaHome = System.getProperty('java.home')
            def javaPath = new File(javaHome, 'bin/java').absolutePath

            options.fork executable: javaPath,
                jvmArgs: [ '-cp', project.configurations.ecj.asPath, 'org.eclipse.jdt.internal.compiler.batch.Main']

            options.define compilerArgs: compilerArgs
        }
}

This is working for us at the moment, but as our build is quite big and a full build from our monorepo took around 10-15 minutes on our development machines, we want to use the build cache. Unfortunately by setting the executable property of the compile task the caching of this tasks will be disabled. This results in a relatively high amount of uncachable tasks (~75% of all tasks in a full build).

So the questions are

  • is it possible to convince gradle to cache the compiler output, although using the executable option of the compile task?
  • or is there another way of using the ecj in our build that is compatible with caching?

Thanks,
Niklas

Hi Niklas,

as you already noted, we currently disable caching if an executable is used for compiling Java. This is due to the fact that we do not know what the real inputs to compilation are when using an executable and the inherent problem that the absolute path to the executable is added as an input to the JavaCompile task, making it effectively impossible to re-use build outputs between different machines.
In order for the build cache to work well with the eclipse compiler, it would need to be modelled as a Java toolchain instead of being an opaque executable. Currently, we are not yet at a point where this is easily possible. The non-easy way is shown for example in the error prone Gradle plugin.
Looking at your build script snippet, I see that you are adding all the configuration of the task in a doFirst snippet. If you do it this way, then Gradle’s incremental build will not see those changes, this means that caching finally won’t be disabled for the tasks at all. Note that this is not encouraged, since all those changes are not seen by Gradle’s incremental build feature and that means that not all inputs to the task may be captured resulting in incorrect builds. When you turn on the build cache this makes matters even worse, since then the outputs are shared between machines and only cleaning the build cache can then fix the incorrectness.
Having said all that, I am quite surprised that javac is this much slower compared to ecj. What version of Gradle are you using? We invested a lot in making Java compilation faster in the latest Gradle releases. Is ecj also faster when used on a clean build? What configuration are you using for javac? Note that we do incremental Java compilation, you might want to try that out, too.

Cheers,
Stefan

Hi Stefan,

thank you for your reply.
I will have a look at the error prone plugin.

I’m wondering too that the javac is so much slower.
We are using gradle 4.1 with the java 8 orcale jdk.
The main problem is, that we have a custom code generator, that generates java code with very big interface inheritance hierarchies, which results in slowing the javac down, so it needs around 40-60 seconds to compile one class that implements an interface of theses hierarchy.
The ejc does this in 1 second. We have already tried incremental compilation, but then the problem remains on our ci server, where we always force a clean build.

Cheers,
Niklas

Hi Stefan,

is there any solution for using build cache and ecj?

We use

compileJava {
	options.fork = true
	options.forkOptions.with {
    	executable = 'java'
    	jvmArgs = ['-classpath', configurations.ecj.asPath, 'org.eclipse.jdt.internal.compiler.batch.Main', '-nowarn']
	}
}

UPDATE: i have found https://github.com/TwoStone/gradle-eclipse-compiler-plugin.

Kind regards,
Daniel