All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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) }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy