Guava classpath conflict and the Gradle daemon

I’m the maintainer of a Gradle plugin for a Java-based documentation tool called DITA Open Toolkit.

The way to run DITA-OT is to basically stick all of its dependencies into the classpath and run Ant. So, in my plugin, I do something like this:

ditaOtDependencies.each {
    org.apache.tools.ant.Project.class.getClassLoader().addURL(it.toURI().toURL())
}

...

ant.ant(antfile: 'dita-ot/build.xml') { ... }

That works fine, but recently DITA-OT added Guava into its list of dependencies. Ever since, builds that use my plugin together with the Gradle daemon have started failing with errors like this:

Could not create service of type FileSnapshotter using TaskExecutionServices.createFileSnapshotter().
> com.google.common.cache.AbstractCache$SimpleStatsCounter cannot be cast to com.google.common.cache.AbstractCache$StatsCounter

(That’s one of many possible Guava-related errors.)

I presume the reason is that Gradle also uses Guava, but a different version than DITA-OT, and when Gradle asks the classloader for Guava, it picks which of the two versions to give at random, and if it’s the wrong version, the build fails.

Here’s a buildfile you can use to reproduce the issue (you must have the Gradle daemon enabled):

defaultTasks 'x'

task x  {
    def url = 'http://central.maven.org/maven2/com/google/guava/guava/19.0/guava-19.0.jar'
    def file = new File('guava-19.0.jar')

    if (!file.exists()) {
        def stream = file.newOutputStream()
        stream << new URL(url).openStream()
        stream.close()
    }

    // Comment this line and the build will succeed.
    org.apache.tools.ant.Project.class.getClassLoader().addURL(file.toURI().toURL())
}

The error is sporadic, so it might not pop up in every single build.

Is there any way around this issue? I could run Ant in a forked JVM, of course, but that’ll mean losing the performance benefit that the Gradle daemon yields. That can be around 170% in some cases, so I’d really like to hold onto that if possible.

GRADLE-1715 is the problem that Gradle runtime dependencies clash with the plugin classpath.

I looked at your plugin and switched to use the internal IsolatedAntBuilder to load the Ant task see https://github.com/lhotari/dita-ot-gradle/commit/a87c93a852872983bd0061366144cdc449ba0d33 . I’m not sure if that works or not since the gradle build in the project didn’t work for me for running the tests (“Task with path ‘:dost:copyInstall’ not found in root project ‘dita-ot-gradle’.”).

Many thanks for the reply and the sample code!

I’m not sure if that works or not since the gradle build in the project didn’t work for me for running the tests (“Task with path ‘:dost:copyInstall’ not found in root project ‘dita-ot-gradle’.”).

Yeah, the tests are a bit silly at the moment, you have to do git submodule update --init --remote first to fetch DITA-OT so that you can run them. You should be able to run the examples in the examples directory, though.

Anyway I tried incorporating your changes into the plugin, but it doesn’t seem to be picking up the classpath, even if I hard-code the paths to the JAR files. For example, if I have:

antBuilder.withClasspath([new File('/opt/dita-ot/lib/dost.jar')]).execute {
    ant.ant(antfile: '/opt/dita-ot/build.xml') {
        ...
    }
}

I get:

java.lang.ClassNotFoundException: org.dita.dost.module.GenMapAndTopicListModule

Which is one of the classes in /opt/dita-ot/lib/dost.jar.

I’ll try to investigate the IsolatedAntBuilder approach a bit more, though.

Regarding GRADLE-1715: Yeah, I actually tried the workaround in the last comment earlier, but if I use it to exclude the Guava JAR, things break because, well, Gradle needs Guava, I guess.

Regarding GRADLE-1715: Yeah, I actually tried the workaround in the last comment earlier, but if I use it to exclude the Guava JAR, things break because, well, Gradle needs Guava, I guess.

Actually, if I add the buildSrc directory suggested in the workaround into the project directory rather than the plugin directory, it does fix the issue. But having to do that makes installing and using the plugin a bit unwieldy.