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

main.name.remal.gradle_plugins.plugins.packed_dependencies.ProcessPackedDependencies.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package name.remal.gradle_plugins.plugins.packed_dependencies

import name.remal.CLASS_FILE_NAME_SUFFIX
import name.remal.SERVICE_FILE_BASE_PATH
import name.remal.createParentDirectories
import name.remal.forceDeleteRecursively
import name.remal.gradle_plugins.api.packed_dependencies.PackedDependencyInfo
import name.remal.gradle_plugins.dsl.BuildTask
import name.remal.gradle_plugins.dsl.extensions.ResolvedDependencyMapping
import name.remal.gradle_plugins.dsl.extensions.getResolvedDependencyMappings
import name.remal.gradle_plugins.dsl.extensions.javaPackageName
import name.remal.sha256
import name.remal.zipContentTo
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity.ABSOLUTE
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes.AASTORE
import org.objectweb.asm.Opcodes.ACC_FINAL
import org.objectweb.asm.Opcodes.ACC_PRIVATE
import org.objectweb.asm.Opcodes.ACC_PUBLIC
import org.objectweb.asm.Opcodes.ACC_STATIC
import org.objectweb.asm.Opcodes.ALOAD
import org.objectweb.asm.Opcodes.ANEWARRAY
import org.objectweb.asm.Opcodes.ARETURN
import org.objectweb.asm.Opcodes.BIPUSH
import org.objectweb.asm.Opcodes.DUP
import org.objectweb.asm.Opcodes.GETSTATIC
import org.objectweb.asm.Opcodes.INVOKESPECIAL
import org.objectweb.asm.Opcodes.INVOKESTATIC
import org.objectweb.asm.Opcodes.PUTSTATIC
import org.objectweb.asm.Opcodes.RETURN
import org.objectweb.asm.Opcodes.V1_8
import org.objectweb.asm.Type.getDescriptor
import org.objectweb.asm.Type.getInternalName
import org.objectweb.asm.Type.getMethodDescriptor
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.IntInsnNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.LdcInsnNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.TypeInsnNode
import org.objectweb.asm.tree.VarInsnNode
import java.io.File
import kotlin.reflect.jvm.javaMethod

@BuildTask
@CacheableTask
class ProcessPackedDependencies : DefaultTask() {

    @InputFiles
    @PathSensitive(ABSOLUTE)
    @SkipWhenEmpty
    lateinit var packedDependencies: Configuration

    @OutputDirectory
    lateinit var outputDir: File

    @TaskAction
    @Suppress("ComplexMethod", "LongMethod")
    protected fun doProcessPackedDependencies() {
        outputDir.forceDeleteRecursively()

        val dependencies = packedDependencies.getResolvedDependencyMappings(project).filter { it.file.isFile || it.file.isDirectory }

        dependencies.forEach { dependency ->
            val sourceFile = dependency.file
            val targetFile = File(outputDir, dependency.resourceName).createParentDirectories()
            if (sourceFile.isFile) {
                sourceFile.copyTo(targetFile)
            } else {
                sourceFile.zipContentTo(targetFile)
            }
        }

        val rootDependencies = dependencies.filter(ResolvedDependencyMapping::isFirstLevel)
        rootDependencies.forEach { dependency ->
            val serviceClassName = buildString {
                append(project.javaPackageName)
                append(".").append(PackedDependencyInfo::class.java.simpleName)
                append("$$").append(sha256("${dependency.group}:${dependency.module}:${dependency.version}:${dependency.classifier}:${dependency.type}"))
            }

            File(outputDir, SERVICE_FILE_BASE_PATH + "/" + PackedDependencyInfo::class.java.name).appendText("\n" + serviceClassName)

            val classNode = ClassNode()
            classNode.version = V1_8
            classNode.access = ACC_PUBLIC
            classNode.name = serviceClassName.replace('.', '/')
            classNode.superName = getInternalName(Any::class.java)
            classNode.interfaces = mutableListOf(getInternalName(PackedDependencyInfo::class.java))
            classNode.fields = mutableListOf()
            classNode.methods = mutableListOf()

            classNode.methods.add(MethodNode(ACC_PUBLIC, "", "()V", null, null).also { method ->
                method.instructions = InsnList().also {
                    it.add(LabelNode())
                    it.add(VarInsnNode(ALOAD, 0))
                    it.add(MethodInsnNode(INVOKESPECIAL, classNode.superName, method.name, method.desc, false))
                    it.add(InsnNode(RETURN))
                }
                method.maxLocals = 1
                method.maxStack = 1
            })

            run {
                val javaMethod = PackedDependencyInfo::getGroup.javaMethod!!
                classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                    it.instructions = InsnList().also {
                        it.add(LabelNode())
                        it.add(LdcInsnNode(dependency.group))
                        it.add(InsnNode(ARETURN))
                    }
                    it.maxLocals = 1
                    it.maxStack = 1
                })
            }

            run {
                val javaMethod = PackedDependencyInfo::getModule.javaMethod!!
                classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                    it.instructions = InsnList().also {
                        it.add(LabelNode())
                        it.add(LdcInsnNode(dependency.module))
                        it.add(InsnNode(ARETURN))
                    }
                    it.maxLocals = 1
                    it.maxStack = 1
                })
            }

            run {
                val javaMethod = PackedDependencyInfo::getVersion.javaMethod!!
                classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                    it.instructions = InsnList().also {
                        it.add(LabelNode())
                        it.add(LdcInsnNode(dependency.version))
                        it.add(InsnNode(ARETURN))
                    }
                    it.maxLocals = 1
                    it.maxStack = 1
                })
            }

            run {
                val childrenDependencies = dependencies.filter { it in rootDependencies }
                val resourceNames = (listOf(dependency) + childrenDependencies).map(ResolvedDependencyMapping::resourceName)
                classNode.fields.add(FieldNode(ACC_PRIVATE or ACC_STATIC or ACC_FINAL, "resourceNames", "Ljava/util/List;", null, null))
                classNode.methods.add(MethodNode(ACC_STATIC, "", "()V", null, null).also { method ->
                    method.instructions = InsnList().also {
                        it.add(LabelNode())
                        it.add(IntInsnNode(BIPUSH, resourceNames.size))
                        it.add(TypeInsnNode(ANEWARRAY, getDescriptor(String::class.java)))
                        resourceNames.forEachIndexed { index, resourceName ->
                            it.add(InsnNode(DUP))
                            it.add(IntInsnNode(BIPUSH, index))
                            it.add(LdcInsnNode(resourceName))
                            it.add(InsnNode(AASTORE))
                        }
                        it.add(MethodInsnNode(INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false))
                        it.add(MethodInsnNode(INVOKESTATIC, "java/util/Collections", "unmodifiableList", "(Ljava/util/List;)Ljava/util/List;", false))
                        it.add(FieldInsnNode(PUTSTATIC, classNode.name, "resourceNames", "Ljava/util/List;"))
                        it.add(InsnNode(RETURN))
                    }
                    method.maxLocals = 1
                    method.maxStack = 4
                })

                val javaMethod = PackedDependencyInfo::getResourceNames.javaMethod!!
                classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
                    it.instructions = InsnList().also {
                        it.add(LabelNode())
                        it.add(FieldInsnNode(GETSTATIC, classNode.name, "resourceNames", "Ljava/util/List;"))
                        it.add(InsnNode(ARETURN))
                    }
                    it.maxLocals = 1
                    it.maxStack = 1
                })
            }

            val classWriter = ClassWriter(0)
            classNode.accept(classWriter)
            File(outputDir, serviceClassName.replace('.', '/') + CLASS_FILE_NAME_SUFFIX).createParentDirectories().writeBytes(classWriter.toByteArray())
        }

        didWork = true
    }

}

private val ResolvedDependencyMapping.resourceName: String
    get() = "META-INF/packed-dependencies/$group/$module/$version${if (classifier.isNotEmpty()) "/$classifier" else ""}/${file.name}${if (file.isDirectory) ".zip" else ""}"




© 2015 - 2024 Weber Informatics LLC | Privacy Policy