Working around a loader constraint violation


(Cédric Champeau) #1

I had an interesting fight with Gradle today. It didn’t last long because I know a bit of the internals of Groovy and Gradle, but I think it is still interesting to ask here.

Basically, I wrote an AST transformation that allows people to write bytecode directly as the method body of a Groovy class: https://github.com/melix/groovy-bytecode-ast

The project compiles fine, then I wanted to use it in another project, using JMH benchmarks. I had the surprise to see Gradle failing with:

> loader constraint violation: loader (instance of groovy/lang/GroovyClassLoader) previously initiated loading for a different type with name "groovyjarjarasm/asm/MethodVisitor"

My workaround was to use the groovy compiler configuration script to tweak the transform classloader that Gradle provides to the Groovy compiler. This transform loader inherits from the FilteringClassLoader (https://github.com/gradle/gradle/blob/1534319e445cb7dc202269cce64c8324d89cf59e/subprojects/language-jvm/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java#L76-76) which does filter the “groovy” and “org.codehaus.groovy” packages.

Since both groovy.jar and groovy-all.jar include the “groovyjarjarasm” package, that rebundles the ASM library, my fix was to add it through the compiler configuration trick, which is to be honest a bit tricky. First it requires to set a configuration script in Gradle:

compileGroovy.groovyOptions.configurationScript = file('gradle/config.groovy')

Then here is the contents of my config.groovy file:

import groovy.transform.CompilationUnitAware
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.AbstractASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformationClass
  import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
  // a compiler configuration that works around a Gradle issue with classloading
withConfig(configuration) {
    ast(Spoofed)
}
  @Retention(RetentionPolicy.RUNTIME)
@GroovyASTTransformationClass("ClassLoaderSpoofer")
@interface Spoofed {}
  @GroovyASTTransformation(phase = CompilePhase.CONVERSION)
class ClassLoaderSpoofer extends AbstractASTTransformation implements CompilationUnitAware {
      @Override
    void visit(final ASTNode[] nodes, final SourceUnit source) {
    }
      @Override
    void setCompilationUnit(final CompilationUnit unit) {
        unit.transformLoader.parent.allowPackage('groovyjarjarasm')
    }
}

It basically tricks the Groovy compiler by making it think it will do an AST transform, but what it does is actually reaching the Gradle classloader and add the groovyjarjarasm to the list of allowed packages.

So my question is, shouldn’t this package be allowed in any case directly in Gradle? Shouldn’t it be the same for the groovyjarjarantlr and groovyjarjarcommonscli packages too?

Thanks!