classpath-collision-detector
classpath-collision-detector copied to clipboard
Feature request: given a set of collisions, report if any are problematic
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?