使用TreeTranslator重命名Kotlin函数无效

16
我正在尝试使用AST(抽象语法树)重写,在构建过程中重命名Java接口中的方法以及Kotlin接口中的函数。在这个问题中,我们忽略了重命名方法/函数为调用带来的影响。为了找到需要重命名的方法/函数,我使用了自定义注解和注解处理器。遵循这些说明,我已经成功地将其应用于Java接口。
我创建了一个新项目,共包含三个模块:应用程序模块、注解模块和注解处理器模块。
应用程序模块是一个Android应用程序,并包含两个单独的Java和Kotlin接口文件,每个文件都有一个带注释的方法/函数。 RenameJava.java
package nl.peperzaken.renametest;

import nl.peperzaken.renameannotation.Rename;

public interface RenameJava {
    @Rename
    void methodToRename();
}

RenameKotlin.kt

package nl.peperzaken.renametest

import nl.peperzaken.renameannotation.Rename

interface RenameKotlin {
    @Rename
    fun functionToRename()
}

注解模块是一个Java库,只包含@Rename注解,并且我们指定它只能用于函数,我们说它只能在源代码中可见。

Rename.kt

package nl.peperzaken.renameannotation

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class Rename

注解处理器模块是一个Java库,仅包含迭代具有注解并对其进行转换的元素的处理器。

RenameProcessor.kt

package nl.peperzaken.renameprocessor

import com.google.auto.service.AutoService
import com.sun.source.util.Trees
import com.sun.tools.javac.processing.JavacProcessingEnvironment
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.tree.TreeTranslator
import com.sun.tools.javac.util.Names
import nl.peperzaken.renameannotation.Rename
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic

@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("nl.peperzaken.renameannotation.Rename")
class RenameProcessor : AbstractProcessor() {

    private lateinit var trees: Trees
    private lateinit var names: Names

    private val visitor = object : TreeTranslator() {
        override fun visitMethodDef(jcMethodDecl: JCTree.JCMethodDecl) {
            super.visitMethodDef(jcMethodDecl)

            // print original declaration
            processingEnv.messager.printMessage(
                Diagnostic.Kind.NOTE,
                jcMethodDecl.toString()
            )

            // Rename declaration
            jcMethodDecl.name = names.fromString("renamed")

            // print renamed declaration
            processingEnv.messager.printMessage(
                Diagnostic.Kind.NOTE,
                jcMethodDecl.toString()
            )

            // commit changes
            result = jcMethodDecl
        }
    }

    @Synchronized
    override fun init(processingEnvironment: ProcessingEnvironment) {
        super.init(processingEnvironment)
        trees = Trees.instance(processingEnvironment)
        val context = (processingEnvironment as JavacProcessingEnvironment).context
        names = Names.instance(context)
    }

    override fun process(set: Set<TypeElement>, roundEnvironment: RoundEnvironment): Boolean {
        // Find elements that are annotated with @Rename
        for (element in roundEnvironment.getElementsAnnotatedWith(Rename::class.java)) {
            val tree = trees.getTree(element) as JCTree
            tree.accept(visitor)
        }
        return true
    }
}

Gradle文件

我在注解处理器的build.gradle中添加了以下内容:

// Add annotation dependency
implementation project(':rename-annotation')
// Used to generate META-INF so the processor can run
compile 'com.google.auto.service:auto-service:1.0-rc4'
kapt "com.google.auto.service:auto-service:1.0-rc4"
// To prevent unresolved references during building. You might not need this dependency.
implementation files("${System.getProperty('java.home')}/../lib/tools.jar")

我在应用程序的build.gradle中添加了以下内容:
compileOnly project(':rename-annotation')
annotationProcessor project(':rename-processor')

build.gradle注解除了默认生成的依赖项外,没有其他依赖项。

我们拥有不同的模块是为了防止注解和处理器被构建到最终的APK中,因为我们只需要在构建过程中使用它们。

输出

日志显示Java接口中的方法已被重命名:

Note: 
  @Rename()
  void methodToRename();
Note: 
  @Rename()
  void renamed();

没有为Kotlin接口生成日志。表明注解处理器没有运行。
当你查看生成的APK的classes.dex时,你会看到以下内容: Output annotationProcessor 您可以看到Java接口的方法已正确重命名。而Kotlin接口的函数没有。即使它在日志中显示出来。
您还将在日志中注意到这个警告:
app: 'annotationProcessor' dependencies won't be recognized as kapt annotation processors. Please change the configuration name to 'kapt' for these artifacts: 'RenameTest:rename-processor:unspecified' and apply the kapt plugin: "apply plugin: 'kotlin-kapt'".
所以让我们按照警告建议做。将`apply plugin:'kotlin-kapt'`添加到应用程序`build.gradle`中,并将`annotationProcessor`更改为`kapt`。同步和重建后输出如下:
Note: 
  @Rename()
  void methodToRename();
Note: 
  @Rename()
  void renamed();
Note: 
  @nl.peperzaken.renameannotation.Rename()
  public abstract void functionToRename();
Note: 
  @nl.peperzaken.renameannotation.Rename()
  public abstract void renamed();

Java和Kotlin文件的日志都出现了,你认为这是成功了吗?但是看一下新生成的APK的classes.dex,你会发现它们仍然保持原样:

Output kapt

问题

有没有办法在最终的APK中获得期望的输出?期望的输出是Java接口中的方法和Kotlin接口中的函数都被重命名。

示例项目链接:https://github.com/peperzaken/kotlin-annotation-rename-test

1个回答

3
Kapt不会直接处理Kotlin文件,而是在Java文件存根上运行注释处理。因此,针对Kotlin文件的AST树中的更改仅对其他注释处理器可见,并且不影响编译。
请注意,Java AST API不是注释处理API(JSR 269)的一部分 - 它实际上是一个内部Javac API,而Kotlinc显然不是Javac。
解决问题的更可靠方法是类文件后处理(或Kotlin编译器插件,但这样就无法用于Java)。
此外,在Kotlin中,您可以使用@JvmName()注释来更改JVM声明名称。

类文件后处理似乎是解决方法,但我宁愿在编译之前更改东西,这样在运行时就不会出现问题。@JvmName() 注释可用于 Kotlin 重命名,但不能在 Java 中使用。另外输出字节码中的 kotlin.Metadata 注释仍然包含原始名称。 - stefana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接