如何使用kotlin-compiler-embeddable分析外部Kotlin项目?
使用kotlin-compiler-embeddable编译源码并访问BindingContext
1. 依赖配置(Gradle)
添加必要依赖到构建脚本:
dependencies { implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.20" implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.20" }
2. 完整代码示例
以下代码会编译一组Kotlin源文件,获取BindingContext,并解析目标类中参数类型的全限定名及PSI类表示:
import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import java.io.File fun main() { // 配置编译参数 val args = K2JVMCompilerArguments().apply { // 替换为你的项目源文件路径 freeArgs = listOf( "src/main/kotlin/ShapeService.kt", "src/main/kotlin/ShapeEndpoint.kt" ) // 配置类路径:包含Kotlin标准库、项目依赖JAR/类文件路径 classpath = listOf( System.getProperty("java.class.path"), // 有外部依赖时添加对应JAR路径 "libs/external-dependency.jar" ).joinToString(File.pathSeparator) } // 初始化编译器 val compiler: CLICompiler<K2JVMCompilerArguments> = K2JVMCompiler() val services = Services.EMPTY val messageCollector = MessageCollector.NONE // 执行编译并获取结果 val result = compiler.compile(args, services, messageCollector) val bindingContext = result.bindingContext ?: error("无法获取BindingContext") val psiFiles = result.psiFiles ?: error("无法获取PSI文件") // 遍历PSI文件,解析目标类参数类型 psiFiles.forEach { psiFile -> if (psiFile is KtFile) { psiFile.declarations.forEach { declaration -> if (declaration is KtClass && declaration.name == "ShapeEndpoint") { declaration.primaryConstructor?.valueParameters?.forEach { param -> if (param.name == "service") { resolveParameterInfo(param, bindingContext) } } } } } } } /** * 解析参数的类型全限定名及对应的PSI类 */ fun resolveParameterInfo(param: KtParameter, bindingContext: BindingContext) { val paramDescriptor = bindingContext[BindingContext.VALUE_PARAMETER, param] as? ValueParameterDescriptor paramDescriptor?.type?.constructor?.declarationDescriptor?.let { descriptor -> if (descriptor is ClassDescriptor) { // 获取类型全限定名 val fqn = descriptor.qualifiedName?.asString() println("参数${param.name}的类型全限定名:$fqn") // 获取对应的PSI类表示 val psiClass = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? KtClass psiClass?.let { println("对应的PSI类:${it.name},所在文件:${it.containingFile.name}") } } } }
3. 关键注意事项
- 类路径配置:必须确保
classpath包含所有依赖类(Kotlin标准库、项目自身类、外部依赖JAR),否则编译器无法解析引用类型,BindingContext会缺失有效语义信息。 - BindingContext作用:它是语义分析的核心载体,关联了PSI元素与对应的语义描述符,通过它才能从PSI参数映射到真实的类型信息。
- 多文件处理:编译时传入所有相关源文件,才能让编译器建立完整的类依赖关系,单个文件编译无法解析跨文件的类型引用。
4. 示例输出
针对代码class ShapeEndpoint(service: ShapeService),运行代码后会输出类似内容:
参数service的类型全限定名:com.example.ShapeService
对应的PSI类:ShapeService,所在文件:ShapeService.kt
内容的提问来源于stack exchange,提问作者mwwojcik
相关产品推荐
相关产品推荐

