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-dependencies.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.concurrent.atomic.AtomicBoolean
import java.util.function.Function
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.commons.ClassRemapper
import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.AnnotationNode
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.1")
        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())
    }
    relativePath = relativePath.replaceFirst(/^META-INF\/versions\/\d+\//, '')
    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
}

DomainObjectSet projectsToCollectClassesRelyingOnDependency = project.objects.domainObjectSet(Project)
project.ext.projectsToCollectClassesRelyingOnDependency = projectsToCollectClassesRelyingOnDependency

allprojects {
    pluginManager.withPlugin('java') {
        projectsToCollectClassesRelyingOnDependency.add(project)

        tasks.withType(AbstractCompile).configureEach { AbstractCompile task ->
            FileTree classFiles = project.fileTree(destinationDirectory)
                .matching { include('**/*.class') }

            task.doLast {
                classFiles.visit { FileVisitDetails details ->
                    if (details.isDirectory()) {
                        return
                    }

                    ClassNode classNode = new ClassNode()

                    AtomicBoolean isGradleInternalApiUsed = new AtomicBoolean()
                    Remapper gradleInternalApiRemapper = new Remapper() {
                        @Override
                        String map(String internalName) {
                            if (internalName.startsWith('org/gradle/') && internalName.contains('/internal/')) {
                                isGradleInternalApiUsed.set(true)
                            }
                            return super.map(internalName)
                        }
                    }
                    ClassRemapper gradleInternalApiClassRemapper = new ClassRemapper(classNode, gradleInternalApiRemapper)

                    details.open().withCloseable {
                        new ClassReader(it).accept(gradleInternalApiClassRemapper, 0)
                    }

                    if (!isGradleInternalApiUsed.get()
                        || isNodeAnnotatedWith(classNode, 'ReliesOnInternalGradleApi')
                    ) {
                        return
                    }

                    classNode.invisibleAnnotations = classNode.invisibleAnnotations ?: []
                    classNode.invisibleAnnotations.add(
                        new AnnotationNode(
                            'Lname/remal/gradle_plugins/toolkit/annotations/ReliesOnInternalGradleApi;'
                        )
                    )

                    ClassWriter classWriter = new ClassWriter(0)
                    classNode.accept(classWriter)
                    details.file.bytes = classWriter.toByteArray()
                }
            }
        }
    }

    pluginManager.withPlugin('java-gradle-plugin') {
        Closure> createClassesRelyingOnDependency = { String annotationSimpleName ->
            return project.objects.mapProperty(String, ClassNode).value(provider {
                FileTree classesFileTree = project.files().asFileTree
                projectsToCollectClassesRelyingOnDependency.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("**/${annotationSimpleName}.class") }

                Map result = 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 (isNodeAnnotatedWith(classNode, annotationSimpleName)
                        || classNode.fields?.any { isNodeAnnotatedWith(it, annotationSimpleName) }
                        || classNode.methods?.any { isNodeAnnotatedWith(it, annotationSimpleName) }
                    ) {
                        String className = relativePathToClassName(fileTreeElement.path)
                        result.put(className, classNode)
                    }
                }

                if (!result.isEmpty()) {
                    classesFileTree
                        .matching {
                            for (String className : result.keySet()) {
                                include(className.replace('.', '/') + '*.class')
                            }
                        }
                        .visit { FileTreeElement fileTreeElement ->
                            if (fileTreeElement.directory) {
                                return
                            }

                            String className = relativePathToClassName(fileTreeElement.path)
                            result.computeIfAbsent(className) {
                                ClassNode classNode = new ClassNode()
                                fileTreeElement.open().withCloseable {
                                    new ClassReader(it).accept(classNode, SKIP_DEBUG | SKIP_FRAMES)
                                }
                                return classNode
                            }
                        }
                }

                return result
            }).with { it.finalizeValueOnRead(); it }
        }

        MapProperty classesRelyingOnInternalGradleApi = createClassesRelyingOnDependency('ReliesOnInternalGradleApi')
        MapProperty classesRelyingOnExternalDependency = createClassesRelyingOnDependency('ReliesOnExternalDependency')


        tasks.named('collectGradlePluginApiDependencies').configure {
            dependsOn('classes')
            dependsOn(provider {
                projectsToCollectClassesRelyingOnDependency.collect { currentProject ->
                    [
                        currentProject.tasks.named('classes'),
                        currentProject.registerResolveSourceSetRuntimeClasspathTask('main'),
                    ]
                }.flatten()
            })
        }

        ; [
            'zzzz-classes-relying-on-gradle-internal-api': classesRelyingOnInternalGradleApi,
            'zzzz-classes-relying-on-external-dependency': classesRelyingOnExternalDependency,
        ].forEach { String group, MapProperty classesRelyingOnDependency ->
            project.nonJavaApiDependencies.addAll(provider {
                return classesRelyingOnDependency.get().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 "$group:$className:$hash"
                }
            })
        }


        project.apply plugin: 'reporting-base'

        ; [
            'reportClassesRelyingOnInternalGradleApi': classesRelyingOnInternalGradleApi,
            'reportClassesRelyingOnExternalDependency': classesRelyingOnExternalDependency,
        ].forEach { String taskName, MapProperty classesRelyingOnDependency ->
            TaskProvider reportTask = tasks.register(taskName) { Task task ->
                task.dependsOn('classes')
                dependsOn(provider {
                    projectsToCollectClassesRelyingOnDependency.collect { currentProject ->
                        [
                            currentProject.tasks.named('classes'),
                            currentProject.registerResolveSourceSetRuntimeClasspathTask('main'),
                        ]
                    }.flatten()
                })

                ListProperty classNamesRelyingOnDependency = project.objects.listProperty(String).value(provider {
                    classesRelyingOnDependency.get()
                        .keySet()
                        .toSorted()
                }).with { it.finalizeValueOnRead(); it }
                task.inputs.property('classNamesRelyingOnDependency', classNamesRelyingOnDependency)

                File reportFile = project.file("${project.reporting.baseDirectory.asFile.get()}/${task.name}.txt")
                task.outputs.file(reportFile).withPropertyName('reportFile')
                doFirst { reportFile.delete() }

                doLast {
                    reportFile.parentFile.mkdirs()
                    reportFile.setText(
                        classNamesRelyingOnDependency.get().join('\n'),
                        'UTF-8'
                    )
                }
            }

            tasks.named('assemble').configure { dependsOn(reportTask) }
            tasks.named('build').configure { dependsOn(reportTask) }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy