二进制兼容性验证器
该工具允许转储 Kotlin 库的 JVM 部分的公共二进制 API,并确保公共二进制 API 未发生会导致二进制不兼容的更改。
内容
要求
二进制兼容性验证器插件需要 Gradle 6.1.1
或更新的版本。
Kotlin 版本 1.6.20
或更新的版本。
设置
二进制兼容性验证器是一个 Gradle 插件,可以通过以下方式添加到您的构建中:
- 在
build.gradle.kts
中
plugins {
id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.16.3"
}
- 在
build.gradle
中
plugins {
id 'org.jetbrains.kotlinx.binary-compatibility-validator' version '0.16.3'
}
将插件应用于根项目构建文件就足够了;所有子项目都会自动配置。
任务
该插件提供了两个任务:
apiDump
- 构建项目并将其公共 API 转储到项目api
子文件夹中。 API 以人类可读的格式转储。如果 API 转储已经存在,它将被覆盖。apiCheck
- 构建项目并检查项目的公共 API 是否与项目api
子文件夹中的黄金值相同。 此任务自动插入到check
管道中,因此build
和check
任务都将在执行时开始检查公共 API。
对于具有多个 JVM 目标的项目,将创建多个子文件夹,例如
api/jvm
和api/android
可选参数
二进制兼容性验证器可以通过以下 DSL 进行额外配置:
Groovy
apiValidation {
/**
* 即使它们包含公共 API,也会被排除在公共 API 转储之外的包。
*/
ignoredPackages += ["kotlinx.coroutines.internal"]
/**
* 从 API 验证中排除的子项目
*/
ignoredProjects += ["benchmarks", "examples"]
/**
* 即使它们包含公共 API,也会被排除在公共 API 转储之外的类(完全限定)。
*/
ignoredClasses += ["com.company.BuildConfig"]
/**
* 一组标记 API 为非公共的注解。
* 通常是各种 `@InternalApi` 注解,它们标记实际上是私有 API 但由于技术原因无法实际私有化。
*/
nonPublicMarkers += ["my.package.MyInternalApiAnnotation"]
/**
* 以编程方式禁用兼容性验证器的标志
*/
validationDisabled = true
/**
* 一个路径,指向项目根目录内部的一个子目录,用于存储转储。
*/
apiDumpDirectory = "api"
}
Kotlin
apiValidation {
/**
* 即使它们包含公共 API,也会被排除在公共 API 转储之外的包。
*/
ignoredPackages.add("kotlinx.coroutines.internal")
/**
* 从 API 验证中排除的子项目
*/
ignoredProjects.addAll(listOf("benchmarks", "examples"))
/**
* 即使它们包含公共 API,也会被排除在公共 API 转储之外的类(完全限定)。
*/
ignoredClasses.add("com.company.BuildConfig")
/**
* 一组标记 API 为非公共的注解。
* 通常是各种 `@InternalApi` 注解,它们标记实际上是私有 API 但由于技术原因无法实际私有化。
*/
nonPublicMarkers.add("my.package.MyInternalApiAnnotation")
/**
* 以编程方式禁用兼容性验证器的标志
*/
validationDisabled = false
/**
* 一个路径,指向项目根目录内部的一个子目录,用于存储转储。
*/
apiDumpDirectory = "aux/validation"
}
生成 jar 的转储
默认情况下,二进制兼容性验证器会从 build/classes
目录分析项目输出类文件来构建 API 转储。
如果您以不同的方式打包这些类,例如通过排除某些类、应用 shadow
插件等,
从原始类文件构建的 API 转储可能不再准确地反映生成的 jar 的内容。
在这种情况下,最好使用生成的 jar 作为 apiBuild
任务的输入:
Kotlin
tasks {
apiBuild {
// "jar" 是生成最终 jar 文件的默认 Jar 任务的名称
// 在多平台项目中,它可能被命名为 "jvmJar"
// 如果您应用了 shadow 插件,它会创建一个名为 "shadowJar" 的任务来生成转换后的 jar
inputJar.value(jar.flatMap { it.archiveFile })
}
}
工作流程
当开始验证您的库的公共 API 时,我们建议采用以下工作流:
- 准备阶段(一次性操作):
- 作为第一步,应用插件,配置它并执行
apiDump
。 - 手动验证您的公共 API。
- 将
.api
文件提交到您的版本控制系统。 - 此时,默认的
check
任务将在运行测试的同时验证公共 API,如果 API 不同将会导致构建失败。
- 作为第一步,应用插件,配置它并执行
- 常规工作流
- 当进行不会影响公共 API 的代码更改时,不需要执行任何额外操作。您的 CI 上的
check
任务将验证所有内容。 - 当进行会影响公共 API 的代码更改时,无论是添加新 API 还是调整现有 API,
check
任务将开始失败。 应该手动执行apiDump
,并验证.api
文件中的差异:只有您预期会更改的签名应该被更改。 - 将生成的
.api
差异与代码更改一起提交。
- 当进行不会影响公共 API 的代码更改时,不需要执行任何额外操作。您的 CI 上的
实验性 KLib ABI 验证支持
KLib 验证支持是实验性的,未来可能会发生变化(适用于 API 和 ABI 转储格式)。 项目必须使用 Kotlin 1.9.20 或更新的版本才能使用此功能。
要验证 Kotlin 库(KLib)的公共 ABI,需要明确启用相应的选项:
apiValidation {
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
klib {
enabled = true
}
}
启用时,KLib支持会为现有的 apiDump
和 apiCheck
任务添加额外的依赖项。
生成的 KLib ABI 转储被放置在 JVM 转储旁边(默认在 api
子文件夹中),文件名为 <项目名称>.klib.api
。
该转储文件将为各个目标生成的所有转储合并在一起,并使用相应的目标名称对特定于某些目标的声明进行注释。
在验证阶段,该文件将与从库的最新版本提取的转储进行比较,并将两个文件之间的任何差异报告为错误。
目前,可选参数部分中描述的所有选项都支持 klibs 。
唯一的注意事项是,所有类名都应该以 JVM 格式指定,例如 package.name.ClassName$SubclassName
。
有关格式及当前实现背后的原因的详细信息,请参阅设计文档。
在 Linux 和 Windows 主机上生成和验证 KLib ABI 转储
目前,只有在 Apple 主机上才支持编译 Apple 特定的目标(如 iosArm64
或 watchosX86
)。
为了方便在 Windows 和 Linux 主机上进行开发,二进制兼容性验证器不会验证当前主机上不支持的目标的 ABI,即使 .klib.api
文件包含这些目标的声明。
可以更改此行为,以在无法编译某些目标的 klibs 时强制出错:
apiValidation {
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
klib {
enabled = true
// 将目标在主机上不受支持视为错误
strictValidation = true
}
}
对于非 Apple 主机上的转储生成(apiDump
任务),二进制兼容性验证器会尝试从为支持的目标生成的转储和项目 api
文件夹中的旧转储(如果有)推断 ABI。
推断的转储可能不匹配实际转储,因此建议在支持所有所需目标的主机上更新转储,如果可能的话。
什么构成公共API
类
如果满足以下所有条件,则类被视为有效公共:
- 它具有公共或受保护的 JVM 访问权限(
ACC_PUBLIC
或ACC_PROTECTED
) - 它具有以下 Kotlin 可见性之一:
- 无可见性(意味着没有对应的 Kotlin 声明)
- public
- protected
- internal,仅当类被
PublishedApi
注解时
- 它不是一个本地类
- 它不是一个带有
when
表交换映射的合成类($WhenMappings
) - 如果该类对应于带有顶级成员的 Kotlin 文件或 多文件外观, 它至少包含一个有效的公共成员
- 如果该类是另一个类的成员,则它包含在 有效公开 的类中
- 如果该类是另一个类的受保护成员,则它包含在 非最终 类中
成员
类的成员(即字段或方法)如果满足以下所有条件,则被视为有效公共:
- 它具有公共或受保护的 JVM 访问权限(
ACC_PUBLIC
或ACC_PROTECTED
) - 它具有以下 Kotlin 可见性之一:
-
无可见性(意味着没有对应的 Kotlin 声明)
-
public
-
protected
-
internal,仅当类被
PublishedApi
注解时
注意,通过
lateinit
属性公开的字段的可见性是其 setter 的可见性。 -
- 如果成员是受保护的,则它包含在 非最终 类中
- 它不是私有字段的合成访问方法
什么构成公共二进制 API 的不兼容变更
类变更
对于一个类,二进制不兼容的变更是:
- 更改完整的类名(包括包和包含类)
- 更改超类,使类不再具有先前的继承链中的超类
- 更改实现的接口集合,使类不再实现之前实现的接口
- 更改以下任何访问标志:
ACC_PUBLIC
、ACC_PROTECTED
、ACC_PRIVATE
- 降低类的可见性ACC_FINAL
- 将非最终类设为最终ACC_ABSTRACT
- 将非抽象类设为抽象ACC_INTERFACE
- 将类更改为接口,反之亦然ACC_ANNOTATION
- 将注解更改为接口,反之亦然
类成员变更
对于类成员,二进制不兼容的变更是:
- 更改其名称
- 更改其描述符(擦除方法的返回类型和参数类型);这包括将字段更改为方法,反之亦然
- 更改以下任何访问标志:
ACC_PUBLIC
、ACC_PROTECTED
、ACC_PRIVATE
- 降低成员的可见性ACC_FINAL
- 将非最终字段或方法设为最终ACC_ABSTRACT
- 将非抽象方法设为抽象ACC_STATIC
- 将实例成员更改为静态,反之亦然
在本地构建项目
为了在 IDE 中构建和运行测试,需要满足两个先决条件:
- Java 11 或更高版本,以使用最新的 ASM
- 所有构建操作在 IDE 中都应委托给 Gradle
贡献
阅读贡献指南。