美文网首页AndroidKotlin
Kotlin-KCP的应用-修改SDK版本号

Kotlin-KCP的应用-修改SDK版本号

作者: guodongAndroid | 来源:发表于2022-05-23 21:35 被阅读0次

    背景

    在 SDK 开发中,一般会暴露获取 SDK 版本号的接口,获取的版本号一般为 String 类型,比如:

    // sdk接口
    interface Sdk {
        fun getVersion(): String
    }
    
    // sdk调用方
    sdk.getVersion()
    

    上述方式可以通过在 gradle.properties 中配置版本号,然后在 build.gradle 中读取版本号生成至 BuildConfig.java 中,例如:

    // gradle.properties
    VERSION=1.0.0.0
    
    // builde.gradle
    android {
        defaultConfig {
            buildConfigField("String", "SDK_VERSION", "\"$VERSION\"")
        }
    }
    
    // SdkImple.kt
    class SdkImpl : Sdk {
        override fun getVersion(): String {
            // 返回 BuildConfig 中的 SDK_VERSION
            return BuildConfig.SDK_VERSION
        }
    }
    

    上述方式在 SDK 发版时只需修改 gradle.properties 中的版本号即可

    但是上述方式有一个弊端:SDK 提供的版本号为 String 类型,第三方根据版本号进行适配开发时不太方便,第三方需要自己实现版本号大小的判断,笔者希望 SDK 自身可以暴露判断版本号大小的接口

    方案

    基于上述需求,SDK 暴露的获取版本号接口就不能返回 String 类型了,Sdk 接口修改如下:

    interface Sdk {
        // 返回一个 Version 对象
        fun getVersion(): Version
    }
    
    class SdkImpl : Sdk {
        override fun getVersion(): Version {
            // 返回 Version 中的 CURRENT
            return Version.CURRENT
        }
    }
    

    下面是 Version 对象的定义[1],版本号规则不尽相同,以下是示例:

    class Version internal constructor(
        private val major: Int, // 主版本 1
        private val minor: Int, // 次版本 0
        private val patch: Int, // 补丁版本 0
        private val extra: Int, // 保留版本 0
        private val suffix: String?, // 后缀版本, 比如:alpha01、beta01
    ) : Comparable<Version> {
    
        private val version = versionOf(major, minor, patch, extra)
    
        // 版本校验
        private fun versionOf(major: Int, minor: Int, patch: Int, extra: Int): Int {
            require(
                major in 0..MAX_COMPONENT_VALUE &&
                        minor in 0..MAX_COMPONENT_VALUE &&
                        patch in 0..MAX_COMPONENT_VALUE &&
                        extra in 0..MAX_COMPONENT_VALUE
            ) {
                "Version components are out of range: $major.$minor.$patch.$extra"
            }
            return major.shl(24) + minor.shl(16) + patch.shl(8) + extra
        }
    
        override fun toString(): String =
            if (suffix.isNullOrEmpty()) "$major.$minor.$patch.$extra" else "$major.$minor.$patch.$extra-$suffix"
    
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            val otherVersion = (other as? Version) ?: return false
            return this.version == otherVersion.version
        }
    
        override fun hashCode(): Int = version
    
        // 版本比较1
        override fun compareTo(other: Version): Int = version - other.version
    
        // 版本比较2
        fun isAtLeast(major: Int, minor: Int): Boolean =
            this.major > major || (this.major == major &&
                    this.minor >= minor)
    
        // 版本比较2
        fun isAtLeast(major: Int, minor: Int, patch: Int): Boolean =
            this.major > major || (this.major == major &&
                    (this.minor > minor || this.minor == minor &&
                            this.patch >= patch))
    
        // 版本比较2
        fun isAtLeast(major: Int, minor: Int, patch: Int, extra: Int): Boolean =
            this.major > major || (this.major == major &&
                    (this.minor > minor || this.minor == minor &&
                            (this.patch > patch || this.patch == patch &&
                                    this.extra >= extra)))
    
        companion object {
            internal const val MAX_COMPONENT_VALUE = 255
    
            // 当前版本
            @JvmField
            val CURRENT: Version = VersionCurrentValue.get()
        }
    }
    
    private object VersionCurrentValue {
        @JvmStatic
        fun get(): Version =
            Version(0, 0, 0, 0, null) // value is written here automatically during build
    }
    

    第三方进行版本适配开发时,可以如下操作,就比较方便了:

    val version = sdk.getVersion()
    println("version = $version")
    
    if (version.isAtLeast(1, 2)) {
        // 当前版本大于等于 1.2.0.0
        // do something
    } else {
        // 当前版本小于 1.2.0.0
        // do something
    }
    

    上述方案是不是比较友好了?:happy:,不知道读者有没有发现,在哪里修改版本号呢?

    细心的读者可能已经发现,Version.CURRENT 是调用的 VersionCurrentValue#get() 方法,VersionCurrentValue#get() 方法会创建 Version 对象的实例,只需要修改 VersionCurrentValue#get() 方法传入版本号即可。等下,每次发版时都要修改 VersionCurrentValue#get() 方法?

    隐隐感觉到一丝不妥,要是哪次发版时忘记修改 VersionCurrentValue#get() 方法,这不惨了:cry:

    “人非圣贤孰能无过” 呢,还是让程序帮我们生成版本号吧,同时兼容方案一:只修改 gradle.properties 即可

    使用 KCP 在编译阶段修改 VersionCurrentValue#get() 方法

    实现

    在上篇 Kotlin-KCP的应用-第二篇 中笔者记录了搭建 KCP 环境的基本步骤,这里不再赘述,有兴趣的读者可以先看下上篇文章

    组织架构

    上图是本项目的组织架构,简单介绍下:

    • sample:包含 Version 及测试类

    • version-plugin-gradle:kcp 中的 gradle plugin 部分

    • version-plugin-kotlin:kcp 中的 kotlin compiler plugin 部分

    sample 模块不做介绍,下面主要实现其他两个模块

    build.gradle.kts - project level

    在项目级别的 build.gradle.kts 脚本中配置插件依赖

    buildscript {
        // 配置 Kotlin 插件唯一ID
        extra["kotlin_plugin_id"] = "com.guodong.android.version.kcp"
    }
    
    plugins {
        kotlin("jvm") version "1.5.31" apply false
        
        // 配置 Gradle 发布插件,可以不再写 META-INF
        id("com.gradle.plugin-publish") version "0.16.0" apply false
        
        // 配置生成 BuildConfig 插件
        id("com.github.gmazzo.buildconfig") version "3.0.3" apply false
    }
    
    allprojects {
        // 配置 Kotlin 插件版本
        version = "0.0.1"
    }
    

    version-plugin-gradle

    首先配置下 build.gradle.kts 脚本

    build.gradle.kts - module level

    plugins {
        id("java-gradle-plugin")
        kotlin("jvm")
        id("com.github.gmazzo.buildconfig")
    }
    
    dependencies {
        implementation(kotlin("gradle-plugin-api"))
    }
    
    buildConfig {
        // 配置 BuildConfig 的包名
        packageName("com.guodong.android.version.kcp.plugin.gradle")
    
        // 设置 Kotlin 插件唯一 ID
        buildConfigField("String", "KOTLIN_PLUGIN_ID", "\"${rootProject.extra["kotlin_plugin_id"]}\"")
    
        // 设置 Kotlin 插件 GroupId
        buildConfigField("String", "KOTLIN_PLUGIN_GROUP", "\"com.guodong.android\"")
    
        // 设置 Kotlin 插件 ArtifactId
        buildConfigField("String", "KOTLIN_PLUGIN_NAME", "\"version-kcp-kotlin-plugin\"")
    
        // 设置 Kotlin 插件 Version
        buildConfigField("String", "KOTLIN_PLUGIN_VERSION", "\"${project.version}\"")
    }
    
    gradlePlugin {
        plugins {
            create("Version") {
                id = rootProject.extra["kotlin_plugin_id"] as String // `apply plugin: "com.guodong.android.version.kcp"`
                displayName = "Version Kcp"
                description = "Version Kcp"
                implementationClass = "com.guodong.android.version.kcp.gradle.VersionGradlePlugin" // 插件入口类
            }
        }
    }
    
    tasks.withType<KotlinCompile> {
        kotlinOptions.jvmTarget = "1.8"
    }
    

    VersionGradlePlugin

    创建 VersionGradlePlugin 实现 KotlinCompilerPluginSupportPlugin 接口

    class VersionGradlePlugin : KotlinCompilerPluginSupportPlugin {
    
        override fun apply(target: Project): Unit = with(target) {
            logger.error("Welcome to guodongAndroid-version kcp gradle plugin.")
            
            // 此处配置 Gradle 插件扩展
            extensions.create("version", VersionExtension::class.java)
        }
    
        // 是否适用, 默认True
        override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true
    
        // 获取 Kotlin 插件唯一ID
        override fun getCompilerPluginId(): String = BuildConfig.KOTLIN_PLUGIN_ID
    
        // 获取 Kotlin 插件 Maven 坐标信息
        override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
            groupId = BuildConfig.KOTLIN_PLUGIN_GROUP,
            artifactId = BuildConfig.KOTLIN_PLUGIN_NAME,
            version = BuildConfig.KOTLIN_PLUGIN_VERSION
        )
    
        // 读取 Gradle 插件扩展信息并写入 SubpluginOption
        override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
            val project = kotlinCompilation.target.project
            val extension = project.extensions.getByType(VersionExtension::class.java)
            return project.provider {
                listOf(
                    SubpluginOption(key = "version", value = extension.version)
                )
            }
        }
    }
    

    因为版本号需要在外部配置传入 Gradle Plugin,这里需要创建 VersionExtension:

    open class VersionExtension {
    
        var version: String = "0.0.0.0"
    
        override fun toString(): String {
            return "VersionExtension(version=$version)"
        }
    }
    

    至此 Gradle 插件编写完成

    version-plugin-kotlin

    接下来编写 Kotlin 编译器插件,首先配置下 build.gradle.kts 脚本

    build.gradle.kts - module level

    plugins {
        kotlin("jvm")
        kotlin("kapt")
        id("com.github.gmazzo.buildconfig")
    }
    
    dependencies {
        // 依赖 Kotlin 编译器库
        compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable")
    
        // 依赖 Google auto service
        kapt("com.google.auto.service:auto-service:1.0")
        compileOnly("com.google.auto.service:auto-service-annotations:1.0")
    }
    
    buildConfig {
        // 配置 BuildConfig 的包名
        packageName("com.guodong.android.version.kcp.plugin.kotlin")
        
        // 设置 Kotlin 插件唯一 ID
        buildConfigField("String", "KOTLIN_PLUGIN_ID", "\"${rootProject.extra["kotlin_plugin_id"]}\"")
    }
    
    tasks.withType<KotlinCompile> {
        kotlinOptions.jvmTarget = "1.8"
    }
    

    VersionCommandLineProcessor

    实现 CommandLineProcessor

    @AutoService(CommandLineProcessor::class)
    class VersionCommandLineProcessor : CommandLineProcessor {
    
        companion object {
            // OptionName 对应 VersionGradlePlugin#applyToCompilation() 传入的 Key
            private const val OPTION_VERSION = "version"
    
            // ConfigurationKey
            val ARG_VERSION = CompilerConfigurationKey<String>(OPTION_VERSION)
        }
    
        // 配置 Kotlin 插件唯一 ID
        override val pluginId: String = BuildConfig.KOTLIN_PLUGIN_ID
    
        // 读取 `SubpluginOptions` 参数,并写入 `CliOption`
        override val pluginOptions: Collection<AbstractCliOption> = listOf(
            CliOption(
                optionName = OPTION_VERSION,
                valueDescription = "string",
                description = "version string",
                required = true,
            )
        )
    
        // 处理 `CliOption` 写入 `CompilerConfiguration`
        override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) {
            when (option.optionName) {
                OPTION_VERSION -> configuration.put(ARG_VERSION, value)
                else -> throw IllegalArgumentException("Unexpected config option ${option.optionName}")
            }
        }
    }
    

    VersionComponentRegistrar

    实现 ComponentRegistrar

    @AutoService(ComponentRegistrar::class)
    class VersionComponentRegistrar(
        private val defaultVersion: String,
    ) : ComponentRegistrar {
    
        companion object {
            internal const val DEFAULT_VERSION = "0.0.0.0"
        }
    
        @Suppress("unused") // Used by service loader
        constructor() : this(DEFAULT_VERSION)
    
        override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
            // 获取日志收集器
            val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
            
            // 获取传入的版本号
            val version = configuration.get(VersionCommandLineProcessor.ARG_VERSION, defaultVersion)
    
            // 输出日志,查看是否执行
            // CompilerMessageSeverity.INFO - 没有看到日志输出
            // CompilerMessageSeverity.ERROR - 编译过程停止执行
            messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, "Welcome to guodongAndroid-version kcp kotlin plugin")
    
            // 此处在 `ClassBuilderInterceptorExtension` 中注册扩展
            ClassBuilderInterceptorExtension.registerExtension(
                project,
                VersionClassGenerationInterceptor(
                    messageCollector = messageCollector,
                    // 传入版本号
                    version = version
                )
            )
        }
    }
    

    VersionClassGenerationInterceptor

    class VersionClassGenerationInterceptor(
        private val messageCollector: MessageCollector,
        private val version: String,
    ) : ClassBuilderInterceptorExtension {
    
        // 拦截 ClassBuilderFactory
        override fun interceptClassBuilderFactory(
            interceptedFactory: ClassBuilderFactory,
            bindingContext: BindingContext,
            diagnostics: DiagnosticSink
            // 自定义 ClassBuilderFactory 委托给 源ClassBuilderFactory
        ): ClassBuilderFactory = object : ClassBuilderFactory by interceptedFactory {
            
            // 复写 newClassBuilder
            override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder {
                // 自定义 ClassBuilder
                return VersionClassBuilder(
                    messageCollector = messageCollector,
                    // 传入版本号
                    version = version,
                    // 传入源ClassBuilder
                    delegate = interceptedFactory.newClassBuilder(origin),
                )
            }
        }
    }
    

    VersionClassBuilder

    class VersionClassBuilder(
        private val messageCollector: MessageCollector,
        private val version: String,
        private val delegate: ClassBuilder,
    ) : DelegatingClassBuilder() {
    
        companion object {
            private const val VERSION_NAME = "com/guodong/android/VersionCurrentValue"
            private const val MIN_COMPONENT_VALUE = 0
            private const val MAX_COMPONENT_VALUE = 255
        }
    
        override fun getDelegate(): ClassBuilder {
            return delegate
        }
    
        override fun newMethod(
            origin: JvmDeclarationOrigin,
            access: Int,
            name: String,
            desc: String,
            signature: String?,
            exceptions: Array<out String>?
        ): MethodVisitor {
    
            val original = super.newMethod(origin, access, name, desc, signature, exceptions)
    
            val thisName = delegate.thisName
            
            // 校验VersionCurrentValue的完全限定名
            if (thisName != VERSION_NAME) {
                return original
            }
    
            // 校验是否在`build.gradle`中设置了版本号
            if (version == VersionComponentRegistrar.DEFAULT_VERSION) {
                messageCollector.report(
                    CompilerMessageSeverity.ERROR,
                    "Missing version, need to set version in build.gradle, like this:\n" +
                            "version {\n" +
                            "\tversion = \"1.0.0.0\"\n" +
                            "}"
                )
            }
    
            // 结构版本号
            val (major, minor, patch, extra, suffix) = parseVersion()
            
            // 返回ASM MethodVisitor
            return VersionMethodVisitor(Opcodes.ASM9, original, major, minor, patch, extra, suffix)
        }
    
        // 解析版本号为`Multiple`
        private fun parseVersion(): Multiple<Int, Int, Int, Int, String?> {
            if (version.isEmpty()) {
                throw IllegalArgumentException("Version must not be empty.")
            }
    
            val major: Int
            val minor: Int
            val patch: Int
            val extra: Int
            val suffix: String?
    
            if (version.contains("-")) {
                val split = version.split("-")
                if (split.size != 2) {
                    throw IllegalArgumentException("Version components must be only contains one `-`.")
                }
    
                val versions = split[0].split(".")
                val length = versions.size
                if (length != 4) {
                    throw IllegalArgumentException("Version components must be four digits, it is [ $version ] now.")
                }
    
                try {
                    major = versions[0].toInt()
                    minor = versions[1].toInt()
                    patch = versions[2].toInt()
                    extra = versions[3].toInt()
                    suffix = split[1]
                } catch (e: NumberFormatException) {
                    val errMsg = "Version components must consist of numbers."
                    val exception = IllegalArgumentException(errMsg)
                    exception.addSuppressed(e)
                    throw exception
                }
            } else {
                val versions = version.split(".")
                val length = versions.size
                if (length != 4) {
                    throw IllegalArgumentException("Version components must be four digits, it is [ $version ] now.")
                }
    
                try {
                    major = versions[0].toInt()
                    minor = versions[1].toInt()
                    patch = versions[2].toInt()
                    extra = versions[3].toInt()
                    suffix = null
                } catch (e: NumberFormatException) {
                    val errMsg = "Version components must consist of numbers."
                    val exception = IllegalArgumentException(errMsg)
                    exception.addSuppressed(e)
                    throw exception
                }
            }
    
            if (suffix.isNullOrEmpty()) {
                messageCollector.report(
                    CompilerMessageSeverity.WARNING,
                    String.format(Locale.CHINA, "version = %d.%d.%d.%d", major, minor, patch, extra)
                )
            } else {
                messageCollector.report(
                    CompilerMessageSeverity.WARNING,
                    String.format(Locale.CHINA, "version = %d.%d.%d.%d-%s", major, minor, patch, extra, suffix)
                )
            }
    
            if (checkVersion(major) || checkVersion(minor) || checkVersion(patch) || checkVersion(extra)) {
                val msg = String.format(
                    Locale.CHINA,
                    "Version components are out of range: %d.%d.%d.%d.",
                    major,
                    minor,
                    patch,
                    extra
                )
                throw IllegalArgumentException(msg)
            }
    
            return Multiple(major, minor, patch, extra, suffix)
        }
    
        private fun checkVersion(version: Int): Boolean {
            return version < MIN_COMPONENT_VALUE || version > MAX_COMPONENT_VALUE
        }
    }
    

    Multiple

    data class Multiple<out A, out B, out C, out D, out E>(
        val first: A,
        val second: B,
        val third: C,
        val fourth: D,
        val fifth: E?
    ) : Serializable {
    
        override fun toString(): String = "($first, $second, $third, $fourth, $fifth)"
    }
    

    VersionMethodVisitor

    class VersionMethodVisitor(
        api: Int,
        mv: MethodVisitor,
        private val major: Int,
        private val minor: Int,
        private val patch: Int,
        private val extra: Int,
        private val suffix: String?
    ) : MethodPatternAdapter(api, mv) {
    
        companion object {
            // 状态
            private const val SEEN_ICONST_0 = 1
            private const val SEEN_ICONST_0_ICONST_0 = 2
            private const val SEEN_ICONST_0_ICONST_0_ICONST_0 = 3
            private const val SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0 = 4
            private const val SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0_ACONST_NULL = 5
    
            // Version完全限定名
            private const val OWNER = "com/guodong/android/Version"
            private const val METHOD_NAME = "<init>"
            private const val METHOD_DESCRIPTOR = "(IIIILjava/lang/String;)V"
        }
    
        /**
         * val version = Version(0, 0, 0, 0, null)
         * ICONST_0
         * ICONST_0
         * ICONST_0
         * ICONST_0
         * ACONST_NULL
         */
        override fun visitInsn(opcode: Int) {
            // 状态机
            when (state) {
                SEEN_NOTHING -> {
                    if (opcode == Opcodes.ICONST_0) {
                        state = SEEN_ICONST_0
                        return
                    }
                }
                SEEN_ICONST_0 -> {
                    if (opcode == Opcodes.ICONST_0) {
                        state = SEEN_ICONST_0_ICONST_0
                        return
                    }
                }
                SEEN_ICONST_0_ICONST_0 -> {
                    if (opcode == Opcodes.ICONST_0) {
                        state = SEEN_ICONST_0_ICONST_0_ICONST_0
                        return
                    }
                }
                SEEN_ICONST_0_ICONST_0_ICONST_0 -> {
                    if (opcode == Opcodes.ICONST_0) {
                        state = SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0
                        return
                    }
                }
                SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0 -> {
                    if (opcode == Opcodes.ACONST_NULL) {
                        state = SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0_ACONST_NULL
                        return
                    }
                }
                SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0_ACONST_NULL -> {
                    if (opcode == Opcodes.ACONST_NULL) {
                        mv.visitInsn(opcode)
                        return
                    }
                }
            }
    
            super.visitInsn(opcode)
        }
    
        override fun visitMethodInsn(
            opcode: Int,
            owner: String,
            name: String,
            descriptor: String,
            isInterface: Boolean
        ) {
    
            val flag = opcode == Opcodes.INVOKESPECIAL
            && OWNER == owner
            && METHOD_NAME == name
            && METHOD_DESCRIPTOR == descriptor
    
            when (state) {
                SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0_ACONST_NULL -> {
                    if (flag) {
                        weaveCode(major)
                        weaveCode(minor)
                        weaveCode(patch)
                        weaveCode(extra)
                        weaveSuffix()
                        state = SEEN_NOTHING
                    }
                }
            }
    
            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
        }
    
        // 补发
        override fun visitInsn() {
            when (state) {
                SEEN_ICONST_0 -> {
                    mv.visitInsn(Opcodes.ICONST_0)
                }
                SEEN_ICONST_0_ICONST_0 -> {
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                }
                SEEN_ICONST_0_ICONST_0_ICONST_0 -> {
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                }
                SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0 -> {
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                }
                SEEN_ICONST_0_ICONST_0_ICONST_0_ICONST_0_ACONST_NULL -> {
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ICONST_0)
                    mv.visitInsn(Opcodes.ACONST_NULL)
                }
            }
            state = SEEN_NOTHING
        }
    
        // 织入版本号
        private fun weaveCode(code: Int) {
            when {
                code <= 5 -> {
                    val opcode = when (code) {
                        0 -> Opcodes.ICONST_0
                        1 -> Opcodes.ICONST_1
                        2 -> Opcodes.ICONST_2
                        3 -> Opcodes.ICONST_3
                        4 -> Opcodes.ICONST_4
                        5 -> Opcodes.ICONST_5
                        else -> Opcodes.ICONST_0
                    }
                    mv.visitInsn(opcode)
                }
                code <= 127 -> {
                    mv.visitIntInsn(Opcodes.BIPUSH, code)
                }
                else -> {
                    mv.visitIntInsn(Opcodes.SIPUSH, code)
                }
            }
        }
    
        // 织入后缀
        private fun weaveSuffix() {
            if (suffix.isNullOrEmpty()) {
                mv.visitInsn(Opcodes.ACONST_NULL)
            } else {
                mv.visitLdcInsn(suffix)
            }
        }
    }
    

    应用

    sample - build.gradle.kts

    plugins {
        kotlin("jvm")
        id("com.guodong.android.version.kcp")
    }
    
    version {
        version = "1.0.0.1"
    }
    

    Test

    fun main() {
        println("version = ${Version.CURRENT}")
    }
    
    // output
    version = 1.0.0.1
    

    happy~

    参考


    1. 参考 KotlinVersion.kt

    相关文章

      网友评论

        本文标题:Kotlin-KCP的应用-修改SDK版本号

        本文链接:https://www.haomeiwen.com/subject/rtddprtx.html