classpath-collision-detector icon indicating copy to clipboard operation
classpath-collision-detector copied to clipboard

Feature request: given a set of collisions, report if any are problematic

Open autonomousapps opened this issue 3 years ago • 0 comments

I've wanted something like this plugin for a while, so thank you for creating it! My use-case is detecting collisions on the buildscript classpath, which has been a source of frustration in the past. So, I modified the task registered by this plugin to use that Configuration and detect collisions. I found hundreds!

// root build.gradle
tasks.named('detectCollisions') {
  configurations.setFrom(buildscript.configurations.getByName('classpath'))
  collisionFilter.exclude(
      '**/*.TXT',
      '**/*.txt',
      '**/*.properties',
      'LICENSE',
      'NOTICE',
      '**/*.kotlin_builtins',
      '**/*.proto',
      '**/*.gif',
      '**/*.xml',
      '**/*.xsd',
      '**/*.dtd',
      '**/*.rng',
  )
}

I manually inspected one of the collisions with javap and it turned out that all the duplicate class files were identical (according to a string comparison using that tool). So then I scripted that to see if there were any issues at all amongst all the collisions:

// root build.gradle
tasks.register('printCollisions', CollisionsTask) {
  // this file was generated with:
  // ./gradlew :detectCollisions &> collisions.txt
  collisions.set(file('collisions.txt'))
}

// Example collision:
//Collision detected! Entry com/sun/xml/bind/v2/model/core/NonElementRef.class present in following JARs:
//   /Users/trobalik/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-core/2.3.0/d044c784e41d026778693fb44a8026c1fd9a7506/jaxb-core-2.3.0.jar
//   /Users/trobalik/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.2/9d70d9b54cbc91b0e647d97af87395f39ea189f9/jaxb-impl-2.3.2.jar
//   /Users/trobalik/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.2/5528bc882ea499a09d720b42af11785c4fc6be2a/jaxb-runtime-2.3.2.jar
abstract class CollisionsTask extends DefaultTask {

  @Inject
  abstract ExecOperations getExecOps()

  @PathSensitive(PathSensitivity.RELATIVE)
  @InputFile
  abstract RegularFileProperty getCollisions()

  //javap -classpath <jar file> <class ref>
  @TaskAction
  def action() {
    def classRegex = ~/^Collision detected! Entry (.+?)\.class present in following JARs:$/

    List<Collision> chunks = []
    def lines = getCollisions().get().asFile.readLines()

    def processingCollision = false
    Collision chunk
    lines.each { line ->
      if (line.startsWith('Collision detected! Entry ')) {
        def m = classRegex.matcher(line)
        if (m.matches()) {
          def cl = m.group(1)
          chunk = new Collision(classRef: cl)
          chunks += chunk

          processingCollision = true
        }
      } else if (processingCollision) {
        if (line.trim() == '') {
          processingCollision = false
        } else {
          chunk.jars += line.trim()
        }
      }
    }

    chunks.find().each { collision ->
      Set<String> decompiled = []
      collision.jars.each { jar ->
        def out = new ByteArrayOutputStream()
        getExecOps().exec {
          it.commandLine('javap', '-classpath', jar, collision.classRef)
          it.standardOutput(out)
        }
        decompiled += out.toString()
      }

      if (decompiled.size() != 1) {
        logger.error("Class file ${collision.classRef} has incompatible definitions")
      }
    }
  }

  @Canonical
  class Collision {
    String classRef
    Set<String> jars = []
  }
}

So what I'm wondering is, are you open to this being a feature of the plugin?

autonomousapps avatar Nov 30 '22 05:11 autonomousapps