name.remal.gradle_plugins.toolkit.build_logic.gradle-plugin-collect-classes-relying-on-internal-gradle-api.gradle Maven / Gradle / Ivy
import static org.objectweb.asm.ClassReader.SKIP_DEBUG
import static org.objectweb.asm.ClassReader.SKIP_FRAMES
import java.security.MessageDigest
import java.util.function.Function
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.LineNumberNode
import org.objectweb.asm.tree.MethodNode
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
buildscript {
dependencies {
classpath platform("org.ow2.asm:asm-bom:9.7")
classpath 'org.ow2.asm:asm-tree'
classpath 'org.ow2.asm:asm-commons'
}
repositories {
mavenCentral()
}
}
if (project.isBuildSrcProject) return
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
Closure relativePathToClassName = { String relativePath ->
relativePath = relativePath.replace('\\', '/').replaceFirst('^/+', '')
if (relativePath.endsWith(".class")) {
relativePath = relativePath.substring(0, relativePath.length() - ".class".length())
}
return relativePath.replace('/', '.')
}
Closure isNodeAnnotatedWith = { node, simpleClassName ->
if (node.visibleAnnotations?.any { it.desc.endsWith("/$simpleClassName;") }
|| node.invisibleAnnotations?.any { it.desc.endsWith("/$simpleClassName;") }
) {
return true
}
return false
}
Closure isNodeAnnotatedWithReliesOnInternalGradleApi = { node ->
return isNodeAnnotatedWith(node, 'ReliesOnInternalGradleApi')
}
DomainObjectSet projectsToCollectClassesRelyingOnInternalGradleApi = project.objects.domainObjectSet(Project)
project.ext.projectsToCollectClassesRelyingOnInternalGradleApi = projectsToCollectClassesRelyingOnInternalGradleApi
allprojects {
pluginManager.withPlugin('java') {
projectsToCollectClassesRelyingOnInternalGradleApi.add(project)
}
pluginManager.withPlugin('java-gradle-plugin') {
MapProperty lazyClassesRelyingOnInternalApi =
project.objects.mapProperty(String, ClassNode)
lazyClassesRelyingOnInternalApi.set(
provider {
FileTree classesFileTree = project.files().asFileTree
projectsToCollectClassesRelyingOnInternalGradleApi.forEach { currentProject ->
for (File runtimeClasspathFile : currentProject.sourceSets.main.runtimeClasspath.files) {
if (runtimeClasspathFile.isDirectory()) {
classesFileTree += project.fileTree(runtimeClasspathFile)
} else if (runtimeClasspathFile.isFile()) {
classesFileTree += project.zipTree(runtimeClasspathFile)
}
}
}
classesFileTree = classesFileTree
.matching { include('**/*.class') }
.matching { exclude('**/ReliesOnInternalGradleApi.class') }
Map classesRelyingOnInternalApi = new TreeMap<>()
classesFileTree.visit { FileTreeElement fileTreeElement ->
if (fileTreeElement.directory) return
ClassNode classNode = new ClassNode()
fileTreeElement.open().withCloseable {
new ClassReader(it).accept(classNode, SKIP_DEBUG | SKIP_FRAMES)
}
if (isNodeAnnotatedWithReliesOnInternalGradleApi(classNode)
|| classNode.fields?.any { isNodeAnnotatedWithReliesOnInternalGradleApi(it) }
|| classNode.methods?.any { isNodeAnnotatedWithReliesOnInternalGradleApi(it) }
) {
String className = relativePathToClassName(fileTreeElement.path)
classesRelyingOnInternalApi.put(className, classNode)
}
}
if (!classesRelyingOnInternalApi.isEmpty()) {
classesFileTree
.matching {
for (String className : classesRelyingOnInternalApi.keySet()) {
include(className.replace('.', '/') + '*.class')
}
}
.visit { FileTreeElement fileTreeElement ->
if (fileTreeElement.directory) return
String className = relativePathToClassName(fileTreeElement.path)
classesRelyingOnInternalApi.computeIfAbsent(className) {
ClassNode classNode = new ClassNode()
fileTreeElement.open().withCloseable {
new ClassReader(it).accept(classNode, SKIP_DEBUG | SKIP_FRAMES)
}
return classNode
}
}
}
return classesRelyingOnInternalApi
}
)
tasks.named('collectGradlePluginApiDependencies').configure {
dependsOn('classes')
dependsOn(
provider {
projectsToCollectClassesRelyingOnInternalGradleApi.collect { currentProject ->
[
currentProject.tasks.named('classes'),
currentProject.registerResolveSourceSetRuntimeClasspathTask('main'),
]
}.flatten()
}
)
}
project.nonJavaApiDependencies.addAll(
provider {
Map classesRelyingOnInternalApi = lazyClassesRelyingOnInternalApi.get()
return classesRelyingOnInternalApi.collect { String className, ClassNode classNode ->
classNode.visibleAnnotations = null
classNode.invisibleAnnotations = null
classNode.visibleTypeAnnotations = null
classNode.invisibleTypeAnnotations = null
classNode.fields?.forEach {
it.visibleAnnotations = null
it.invisibleAnnotations = null
it.visibleTypeAnnotations = null
it.invisibleTypeAnnotations = null
}
classNode.methods?.forEach {
it.visibleAnnotations = null
it.invisibleAnnotations = null
it.visibleTypeAnnotations = null
it.invisibleTypeAnnotations = null
it.visibleLocalVariableAnnotations = null
it.invisibleLocalVariableAnnotations = null
it.visibleParameterAnnotations = null
it.invisibleParameterAnnotations = null
}
classNode.fields = classNode.fields?.toSorted(
Comparator.comparing((Function) { it.name })
)
classNode.methods = classNode.methods?.toSorted(
Comparator.comparing((Function) { it.name })
.thenComparing((Function) { it.desc })
)
classNode.methods?.forEach { MethodNode methodNode ->
Iterator instructions = (methodNode.instructions ?: []).iterator()
while (instructions.hasNext()) {
def instruction = instructions.next()
if (instruction instanceof LineNumberNode) {
instructions.remove()
}
}
}
ClassWriter classWriter = new ClassWriter(classNode.api)
classNode.accept(classWriter)
byte[] bytes = classWriter.toByteArray()
MessageDigest messageDigest = MessageDigest.getInstance('SHA-256')
messageDigest.update(className.getBytes('UTF-8'))
messageDigest.update(bytes)
messageDigest.update('\n'.getBytes('UTF-8'))
String hash = new BigInteger(1, messageDigest.digest()).toString(16)
return "zzzz-classes-that-rely-on-gradle-internal-api:$className:$hash"
}
}
)
project.apply plugin: 'reporting-base'
TaskProvider reportTask = tasks.register('reportClassesRelyingOnInternalApi') { Task task ->
task.dependsOn('classes')
dependsOn(
provider {
projectsToCollectClassesRelyingOnInternalGradleApi.collect { currentProject ->
[
currentProject.tasks.named('classes'),
currentProject.registerResolveSourceSetRuntimeClasspathTask('main'),
]
}.flatten()
}
)
ListProperty classNamesRelyingOnInternalApi = project.objects.listProperty(String)
task.inputs.property('classNamesRelyingOnInternalApi', classNamesRelyingOnInternalApi)
classNamesRelyingOnInternalApi.set(
provider {
lazyClassesRelyingOnInternalApi.get()
.keySet()
.toSorted()
}
)
File reportFile = project.file("${project.reporting.baseDir}/${task.name}.txt")
task.outputs.file(reportFile).withPropertyName('reportFile')
doFirst { reportFile.delete() }
doLast {
reportFile.parentFile.mkdirs()
reportFile.setText(
classNamesRelyingOnInternalApi.get().join('\n'),
'UTF-8'
)
}
}
tasks.named('assemble').configure { dependsOn(reportTask) }
tasks.named('build').configure { dependsOn(reportTask) }
}
}