无法执行自定义插件,查看字节码

来源:3-4 ASM字节码实践-登堂入室

慕斯卡4676759

2023-09-19

我将您的代码复制到我的项目中执行

class NavTransform(target: Project) :Transform() {
private val navDatas = mutableListOf()
private var project = target;
companion object {
const val NAV_RUNTIME_DESTINATION =
"Lcom/example/jetpack_android_online/plugin/runtime/NavDestination"
const val NAV_RUNTIME_NAV_TYPE =
"Lcom/example/jetpack_android_online/plugin/runtime/NavDestination$Navtype"
private const val KEY_ROUTE = "route"
private const val KEY_TYPE = "type"
private const val KEY_STARTER = "asStarter"
private const val NAV_RUNTIME_PKG_NAME: String = "com.example.nav_plugin_runtime"
private const val NAV_RUNTIME_REGISTRY_CLASS_NAME: String = "NavRegistry"
private const val NAV_RUNTIME_NAV_DATA_CLASS_NAME: String = "NavData"
private const val NAV_RUNTIME_NAV_LIST: String = "navList"
private const val NAV_RUNTIME_MODULE_NAME: String = “nav-plugin-runtime”
}

override fun getName(): String {
    return "NavTransform"
}

override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
    return TransformManager.CONTENT_CLASS
}

override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
    return TransformManager.SCOPE_FULL_PROJECT
}

override fun isIncremental(): Boolean {
    return false
}

override fun transform(transformInvocation: TransformInvocation?) {
    super.transform(transformInvocation)
    val inputs = transformInvocation?.inputs ?: return
    val outputProvider = transformInvocation.outputProvider
    outputProvider.deleteAll()
    inputs.forEach {
        //1. 对inputs -->directory-->class 文件进行遍历
        //2 .对inputs -->jar-->class 文件进行遍历
        it.directoryInputs.forEach { it ->
            handleDirectoryClasses(it.file)
            val outputDir = outputProvider.getContentLocation(
                it.name,
                it.contentTypes,
                it.scopes,
                Format.DIRECTORY
            )
            if (it.file.isFile) {
                FileUtils.copyFile(it.file, outputDir)
            } else {
                FileUtils.copyDirectory(it.file, outputDir)
            }
        }
        it.jarInputs.forEach { it ->
            handleJarClasses(it.file)
            val outputDir = outputProvider.getContentLocation(
                it.name,
                it.contentTypes,
                it.scopes,
                Format.JAR
            )
            FileUtils.copyFile(it.file, outputDir)
        }
    }
    generateNavRegistry()
}

private fun generateNavRegistry() {
    // 利用kotlinPoet生成NavRegistry.kt文件,存放在nav-plugin-runtime模块下;
    // 用于记录项目中所有的路由节点数据

    //1. 生成成员变量val navList:ArrayList<NavData>
    val navData = ClassName(NAV_RUNTIME_PKG_NAME, NAV_RUNTIME_NAV_DATA_CLASS_NAME)
    val arrayList = ClassName("kotlin.collections", "ArrayList")

    //2.生成get 方法返回值类型List<NavData>
    val list = ClassName("kotlin.collections", "List")
    val arrayListOfNavData = arrayList.parameterizedBy(navData)
    val listOfNavData = list.parameterizedBy(navData)

    //3. 生成 object Class init{ 代码块 }
    val statements = java.lang.StringBuilder()
    navDatas.forEach {
        statements.append(
            String.format(
                "navList.add(NavData(\"%s\",\"%s\",%s,%s))",
                it.route,
                it.className,
                "it.asStarter",
                it.type
            )
        )
        statements.append("\n")
    }

    // 4. 向 object class 添加成员属性navList并且进行初始化赋值
    val property =
        PropertySpec.builder(NAV_RUNTIME_NAV_LIST, arrayListOfNavData, KModifier.PRIVATE)
            .initializer(CodeBlock.builder().addStatement("ArrayList<NavData>()").build())
            .build()

    // 5.构建get方法 并且生成代码块
    val function = FunSpec.builder("get").returns(listOfNavData).addCode(
        CodeBlock.builder()
            .addStatement("val list = ArrayList<NavData>()\n list.addAll(navList)\n return list\n")
            .build()
    ).build()

    // 6. 构建 object NavRegistry class. 并且填充属性、int{}  get 方法
    val typeSpec = TypeSpec.objectBuilder(NAV_RUNTIME_REGISTRY_CLASS_NAME)
        .addProperty(property)
        .addInitializerBlock(CodeBlock.builder().addStatement(statements.toString()).build())
        .addFunction(function)
        .build()

    // 7. 生成文件、添加注释和导包
    val fileSpec = FileSpec.builder(NAV_RUNTIME_PKG_NAME, NAV_RUNTIME_REGISTRY_CLASS_NAME)
        .addComment("this file is generated by auto,please do not modify!!!")
        .addType(typeSpec)
        .addImport(NavDestination.NavType::class.java, "Fragment", "Dialog", "Activity", "None")
        .build()

    // 8. 写入文件
    val runtimeProject = project.rootProject.findProject(NAV_RUNTIME_MODULE_NAME)
    assert(runtimeProject == null) {
        throw GradleException("cant found $NAV_RUNTIME_MODULE_NAME")
    }
    val sourceSet = runtimeProject!!.extensions.findByName("sourceSets") as SourceSetContainer
    val outputFileDir = sourceSet.first().java.srcDirs.first().absoluteFile
    println("NavTransform outputFileDir:${outputFileDir.absolutePath}")
    fileSpec.writeTo(outputFileDir)
}


private fun handleJarClasses(file: File) {
    println("NavTransform handleJarClasses:${file.name}")
    val zipFile = ZipFile(file)
    zipFile.stream().forEach {
        if (it.name.endsWith("class", true)) {
            println("NavTransform handleJarClasses-zipEntry:${it.name}")
            val inputStream = zipFile.getInputStream(it)
            visitClass(inputStream)
            inputStream.close()
        }
    }
    zipFile.close()
}

private fun handleDirectoryClasses(file: File) {
    if (file.isDirectory) {
        file.listFiles()?.forEach {
            handleDirectoryClasses(it)
        }
    } else if (file.extension.endsWith("class", true)) {
        val inputStream = FileInputStream(file)
        println("NavTransform handleDirectoryClasses-zipEntry:${file.name}")
        visitClass(inputStream)
        inputStream.close()
    }
}

private fun visitClass(inputStream: InputStream) {
    val classReader = ClassReader(inputStream)
    val classVisitor = object : ClassVisitor(Opcodes.ASM9) {
        override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
            // return super.visitAnnotation(descriptor, visible)

            println("visitAnnotation1:$descriptor")
            println("visitAnnotation2:$NAV_RUNTIME_DESTINATION")
            if (descriptor != NAV_RUNTIME_DESTINATION) {
                return object : AnnotationVisitor(Opcodes.ASM9) {
                }
            }

            val annotationVisitor = object : AnnotationNode(Opcodes.ASM9, "NavDestination") {
                var route = ""
                var asStarter = false
                var type = NavDestination.NavType.None
                override fun visit(name: String?, value: Any?) {
                    super.visit(name, value)
                    if (name == KEY_ROUTE) {
                        route = value as String
                    } else if (name == KEY_STARTER) {
                        asStarter = value as Boolean
                    }
                }

                override fun visitEnum(name: String?, descriptor: String?, value: String?) {
                    super.visitEnum(name, descriptor, value)
                    if (name == KEY_TYPE) {
                        assert(value == null) {
                            throw GradleException("NavDestination\$type must be one of Fragment,Activity,Dialog")
                        }
                        type = NavDestination.NavType.valueOf(value!!)
                    }
                }

                override fun visitEnd() {
                    super.visitEnd()
                    val navData =
                        NavData(route, classReader.className.replace("/", "."), type)
                    navDatas.add(navData)
                }
            }as AnnotationVisitor

            return annotationVisitor
        }
    }
    classReader.accept(classVisitor, EXPAND_FRAMES)
}

}
在 HomeFragment 中添加@NavDestination(type = NavDestination.NavType.Fragment, route = “home_fragment”) 运行后还是报同样的错误。我确认插件已经发布到本地库中。
图片描述
下面是我的项目配置:
Gradle :7.4.1

写回答

1回答

LovelyChubby

2023-09-21

这很显然是你本地有报错,把插件先注释掉,👀编译会不会保存吧
0
0

全新版Jetpack进阶提升,系统性落地短视频App

全新版Jetpack进阶提升,系统性落地短视频App

323 学习 · 114 问题

查看课程