The major feature shipped with the release 0.15.0 of the Binary Compatibility Validator is Kotlin libraries (or KLibs) ABI validation support.
This support, however, comes with a few changes that break the plugin's compatibility or changes its behavior:
- In multiplatform projects, empty modules are no longer ignored during ABI validation;
- ABI file names (
api/<project name>.api
) are no longer treated in a case-insensitive way; - Gradle tasks provided by the plugin changed their API.
If you have a multiplatform project with modules having no sources, configure BCV Gradle tasks manually, or have an ABI file with a name in a case different from how the project was named in the settings, please check the following steps for smooth migration.
Previously, if there was an empty Gradle module/project (for example, test-only module), BCV ignored validation of such a module if the multiplatform platform plugin was applied there. However, BCV behaved differently for similar projects with the JVM-plugin applied.
Starting from 0.15.0
behavior was aligned and now BCV no longer ignores validation of empty modules/projects.
If you have such a module, please either generate and commit (empty) ABI dump file for it by running
./gradlew apiDump
, or consider excluding the module from validation using ignoredProjects
setting:
apiValidation {
ignoredProjects += "empty-module"
}
Starting from the version 0.15.0
, name of an ABI dump file stored in the project's directory
has to match project's name. Previously, a project named gradle-project
might use an ABI file
named Gradle-Project.api
, but now it has to be gradle-project.api
.
On case-insensitive filesystems (Windows and macOS filesystems are case-insensitive) the change won't manifest itself, but on case-sensitive filesystems (Linux filesystems) the validation will start failing due to a missing ABI dump file error.
You can either rename the existing file or delete it and run apiDump
task.
If you're using git SCV, it's highly recommended to use git mv
command to rename a file, or a pair of git rm
and
git add
commands, if you're deleting the old file and then generating a new one. When done differently, git may
not reflect changes in a repository index (it's specific to case-insensitive filesystems).
If you're using another SCV, please consult its documentation for details on how to deal with file renaming on case-insensitive filesystems.
All Gradle tasks that existed before are still presented and in use, but some of them changed their API to use Gradle Properties-based configuration. There are several advantages of using Gradle Property instead of regular Kotlin properties, but the main one is that it improves dependency tracking between tasks. You can Gradle docs for more details on the subject.
The following task-classes changed their API:
- KotlinApiBuildTask
ignoredPackages
,nonPublicMarkers
,ignoredClasses
,publicPackages
,publicMarkers
,publicClasses
are allSetProperty<String>
instances now;outputApiDir
was renamed tooutputApiFile
and becameRegularFileProperty
instance; now it should point to the generated file instead of a directory holding it;inputClassesDirs
andinputDependencies
are bothConfigurableFileCollection
instances now.
- KotlinApiCompareTask
projectApiFile
andgeneratedApiFile
are bothRegularFileProperty
instances now.
If you were configuring KotlinApiBuildTask.outputApiDir
, KotlinApiCompareTask.projectApiFile
or
KotlinApiCompareTask.generatedApiFile
by assigning a file instance to it, now these properties could only
be configured by using RegularFileProperty setters.
Value assignments to KotlinApiBuildTask.inputClassesDirs
and KotlinApiBuildTask.inputDependencies
properties have
to be replaced with ConfigurableFileCollection setters.
All KotlinApiBuildTask
's filters could be configured by using SetProperty setters
now.
Below is an example of how task configuration needs to be updated:
// Old configuration
val buildTask = tasks.register("buildApi", KotlinApiBuildTask::class.java) {
val classes = compilation.output.classesDirs
it.inputClassesDirs = files(classes)
it.inputDependencies = files(classes)
it.outputApiDir = project.layout.buildDirectory.get().asFile
it.ignoredPackages = setOf("org.example")
}
val checkTask = tasks.register("checkApi", KotlinApiCompareTask::class.java) {
it.projectApiFile = project.layout.projectDirectory.dir("api").file("gradle-project.api").asFile
it.generatedApiFile = project.layout.buildDirectory.get().asFile.resolve("dump.api")
it.dependsOn(buildTask)
}
// New configuration
val buildTask = tasks.register("buildApi", KotlinApiBuildTask::class.java) {
val classes = compilation.output.classesDirs
it.inputClassesDirs.from(classes)
it.inputDependencies.from(classes)
it.outputApiFile.set(project.layout.buildDirectory.map { it.file("gradle-project.api") })
it.ignoredPackages.set(setOf("org.example"))
}
val checkTask = tasks.register("checkApi", KotlinApiCompareTask::class.java) {
it.projectApiFile.set(project.layout.projectDirectory.dir("api").file("gradle-project.api"))
it.generatedApiFile.set(buildTask.flatMap { it.outputApiFile })
}