Kotlin-KCP的应用-第二篇
前言
接Kotlin-KCP的应用-第一篇 ,本文是第二篇,以下是本文的目标:
记录如何简单搭建 KCP 开发环境
使用 KCP 解决第一篇中的问题
何为KCP?为何不使用KSP?
KSP
KSP
即 Kotlin Symbol Processing(Kotlin符号处理器)
,KSP 目前只能生成代码,不能修改字节码,第一篇中的问题需要修改字节码,因此 KSP 不能满足需求
KCP
KCP
即 Kotlin Compiler Plugin(Kotlin编译器插件)
,在 kotlinc
过程中提供 hook 时机,在此期间可以生成代码、修改字节码等
标准的 KCP 架构如下:
Plugin
Gradle 插件,与 Kotlin 无关,在 build.gradle 脚本中提供一个入口
通过 Gradle 扩展设置配置信息
Subplugin
介于 Gradle 和 Kotlin 直接的 APIs 接口
读取 Gradle 扩展配置信息并写入 SubpluginOptions
定义编译器插件的唯一ID
定义 Kotlin 插件的 Maven 坐标信息,便于编译器下载它
CommandLinProcessor
设置 Kotlin 插件唯一 ID
读取 kotlinc -Xplugin 参数
读取 SubpluginOptions
配置信息,并写入 CompilerConfigurationKeys
ComponentRegistrar
读取 CompilerConfigurationKeys
注册 Extension
到各编译流程
Extension
生成代码
修改字节码
多种类型的扩展,比如
ExpressionCodegenExtension
ClassBuilderInterceptorExtension
StorageComponentContainerContributor
IrGenerationExtension
实现KCP
目标
根据 KCP 的架构,下面一一进行实现
上图是本仓库架构,旨在通过 KCP 在 Java 字节码中 @Hide
注解目标上设置 ACC_SYNTHETIC
标识,使其在 Java 中不能正常调用,达到隐藏 API 的效果
build.gradle - project level
在项目级别的 build.gradle 脚本中配置插件依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 buildscript { ext.kotlin_plugin_id = "com.guodong.android.mask.kcp" ext.plugin_version = 'x.x.x' } plugins { id("com.gradle.plugin-publish" ) version "0.16.0" apply false id("com.github.gmazzo.buildconfig" ) version "3.0.3" apply false id 'org.jetbrains.kotlin.jvm' version '1.6.10' apply false }
plugin-gradle
接下来编写 Gradle 插件,此插件对应 KCP 架构中的 Plugin
和 Subplugin
首先配置下 build.gradle.kts 脚本
build.gradle.kts - module level
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 plugins { id("java-gradle-plugin" ) kotlin("jvm" ) id("com.github.gmazzo.buildconfig" ) } dependencies { implementation(kotlin("gradle-plugin-api" )) } buildConfig { packageName("com.guodong.android.mask.kcp.gradle" ) buildConfigField("String" , "KOTLIN_PLUGIN_ID" , "\"${rootProject.extra["kotlin_plugin_id" ]} \"" ) buildConfigField("String" , "KOTLIN_PLUGIN_GROUP" , "\"com.guodong.android\"" ) buildConfigField("String" , "KOTLIN_PLUGIN_NAME" , "\"mask-kcp-kotlin-plugin\"" ) buildConfigField("String" , "KOTLIN_PLUGIN_VERSION" , "\"${rootProject.extra["PLUGIN_VERSION" ]} \"" ) } gradlePlugin { plugins { create("Mask" ) { id = rootProject.extra["kotlin_plugin_id" ] as String displayName = "Mask Kcp" description = "Mask Kcp" implementationClass = "com.guodong.android.mask.kcp.gradle.MaskGradlePlugin" } } } tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" }
MaskGradlePlugin
创建 MaskGradlePlugin
实现 KotlinCompilerPluginSupportPlugin
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class MaskGradlePlugin : KotlinCompilerPluginSupportPlugin { override fun apply (target: Project ) = with(target) { logger.error("Welcome to guodongAndroid mask kcp gradle plugin." ) } override fun isApplicable (kotlinCompilation: KotlinCompilation <*>) : Boolean = true override fun getCompilerPluginId () : String = BuildConfig.KOTLIN_PLUGIN_ID override fun getPluginArtifact () : SubpluginArtifact = SubpluginArtifact( groupId = BuildConfig.KOTLIN_PLUGIN_GROUP, artifactId = BuildConfig.KOTLIN_PLUGIN_NAME, version = BuildConfig.KOTLIN_PLUGIN_VERSION, ) override fun applyToCompilation (kotlinCompilation: KotlinCompilation <*>) : Provider<List<SubpluginOption>> { val project = kotlinCompilation.target.project return project.provider { emptyList() } } }
至此 Gradle 插件编写完成,是不是很简单😄
plugin-kotlin
接下来编写 Kotlin 编译器插件,此插件对应 KCP 架构中的 CommandLineProcessor
、 ComponentRegistrar
和 Extension
首先配置下 build.gradle.kts 脚本
build.gradle.kts - module level
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 plugins { kotlin("jvm" ) kotlin("kapt" ) id("com.github.gmazzo.buildconfig" ) } dependencies { compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable" ) kapt("com.google.auto.service:auto-service:1.0" ) compileOnly("com.google.auto.service:auto-service-annotations:1.0" ) } buildConfig { packageName("com.guodong.android.mask.kcp.kotlin" ) buildConfigField("String" , "KOTLIN_PLUGIN_ID" , "\"${rootProject.extra["kotlin_plugin_id" ]} \"" ) } tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" }
根据 KCP 架构,下面实现 CommandLineProcessor
MaskCommandLineProcessor
创建 MaskCommandLineProcessor
实现 CommandLineProcessor
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @AutoService(CommandLineProcessor::class) class MaskCommandLineProcessor : CommandLineProcessor { override val pluginId: String = BuildConfig.KOTLIN_PLUGIN_ID override val pluginOptions: Collection<AbstractCliOption> = emptyList() override fun processOption ( option: AbstractCliOption , value: String , configuration: CompilerConfiguration ) { super .processOption(option, value, configuration) } }
CommandLineProcessor
编写完成
接下来实现 ComponentRegistrar
MaskComponentRegistrar
创建 MaskComponentRegistrar
实现 ComponentRegistrar
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @AutoService(ComponentRegistrar::class) class MaskComponentRegistrar : ComponentRegistrar { override fun registerProjectComponents ( project: MockProject , configuration: CompilerConfiguration ) { val messageCollector = configuration.get (CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) messageCollector.report( CompilerMessageSeverity.WARNING, "Welcome to guodongAndroid mask kcp kotlin plugin" ) ClassBuilderInterceptorExtension.registerExtension( project, MaskClassGenerationInterceptor(messageCollector) ) } }
最后实现 Extension
MaskClassGenerationInterceptor
创建 MaskClassGenerationInterceptor
实现 ClassBuilderInterceptorExtension
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MaskClassGenerationInterceptor ( private val messageCollector: MessageCollector, ) : ClassBuilderInterceptorExtension { override fun interceptClassBuilderFactory ( interceptedFactory: ClassBuilderFactory , bindingContext: BindingContext , diagnostics: DiagnosticSink ) : ClassBuilderFactory = object : ClassBuilderFactory by interceptedFactory { override fun newClassBuilder (origin: JvmDeclarationOrigin ) : ClassBuilder { return MaskClassBuilder(messageCollector, interceptedFactory.newClassBuilder(origin)) } } }
MaskClassBuilder
创建 MaskClassBuilder
继承 DelegatingClassBuilder
,实现 getDelegate
方法,复写 newField
和 newMethod
方法
getDelegate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class MaskClassBuilder ( private val messageCollector: MessageCollector, private val delegate: ClassBuilder ) : DelegatingClassBuilder() { private val annotations: List<FqName> = listOf( FqName("com.guodong.android.mask.api.kt.Hide" ), FqName("com.guodong.android.mask.api.Hide" ) ) override fun getDelegate () : ClassBuilder = delegate }
newField
处理 @Hide
注解目标为字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class MaskClassBuilder ( private val messageCollector: MessageCollector, private val delegate: ClassBuilder ) : DelegatingClassBuilder() { private val annotations: List<FqName> = listOf( FqName("com.guodong.android.mask.api.kt.Hide" ), FqName("com.guodong.android.mask.api.Hide" ) ) override fun newField ( origin: JvmDeclarationOrigin , access: Int , name: String , desc: String , signature: String ?, value: Any ? ) : FieldVisitor { val field = origin.descriptor as ? FieldDescriptor ?: return super .newField(origin, access, name, desc, signature, value) if (annotations.none { field.annotations.hasAnnotation(it) }) { return super .newField(origin, access, name, desc, signature, value) } messageCollector.report( CompilerMessageSeverity.WARNING, "Mask Class = ${delegate.thisName} , fieldName = $name , originalAccess = $access " ) val maskAccess = access + Opcodes.ACC_SYNTHETIC messageCollector.report( CompilerMessageSeverity.WARNING, "Mask Class = ${delegate.thisName} , fieldName = $name , maskAccess = $maskAccess " ) return super .newField(origin, maskAccess, name, desc, signature, value) } }
newMethod
处理 @Hide
注解目标为方法/函数,处理逻辑与字段类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 override fun newMethod ( origin: JvmDeclarationOrigin , access: Int , name: String , desc: String , signature: String ?, exceptions: Array <out String >? ) : MethodVisitor { val function = origin.descriptor as ? FunctionDescriptor ?: return super .newMethod(origin, access, name, desc, signature, exceptions) if (annotations.none { function.annotations.hasAnnotation(it) }) { return super .newMethod(origin, access, name, desc, signature, exceptions) } messageCollector.report( CompilerMessageSeverity.WARNING, "Mask Class = ${delegate.thisName} , methodName = $name , originalAccess = $access " ) val maskAccess = access + Opcodes.ACC_SYNTHETIC messageCollector.report( CompilerMessageSeverity.WARNING, "Mask Class = ${delegate.thisName} , methodName = $name , maskAccess = $maskAccess " ) return super .newMethod(origin, maskAccess, name, desc, signature, exceptions) }
至此 Kotlin 编译器插件完成:happy:
应用KCP
build.gradle - project level
1 2 3 4 5 6 buildscript { ext.plugin_version = 'x.x.x' dependencies { classpath "com.guodong.android:mask-kcp-gradle-plugin:${plugin_version}" } }
lib-kotlin/build.gradle - module level
1 2 3 4 5 6 7 8 9 # lib-kotlin plugins { id 'com.android.library' id 'kotlin-android' id 'kotlin-kapt' id 'maven-publish' id 'com.guodong.android.mask.kcp' }
lib-kotlin
1 2 3 4 5 6 interface InterfaceTest { @Hide fun testInterface () }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class KotlinTest (a: Int ) : InterfaceTest { @Hide constructor () : this (-2 ) companion object { @JvmStatic fun newKotlinTest () = KotlinTest() } private val binding: LayoutKotlinTestBinding? = null var a = a @Hide get @Hide set fun getA1 () : Int { return a } fun test () { a = 1000 } override fun testInterface () { println("Interface function test" ) } }
app
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # MainActivity.java private void testKotlinLib () { KotlinTest test = KotlinTest.newKotlinTest(); Log.e(TAG, "testKotlinLib: before --> " + test.getA1()); test.test(); Log.e(TAG, "testKotlinLib: after --> " + test.getA1()); test.testInterface(); InterfaceTest interfaceTest = test; interfaceTest.testInterface(); }
happy:happy:
参考文献