Skip to content

Kotlin ABI compatibility checking#108

Open
sugarmanz wants to merge 8 commits into
mainfrom
kotlin-abi
Open

Kotlin ABI compatibility checking#108
sugarmanz wants to merge 8 commits into
mainfrom
kotlin-abi

Conversation

@sugarmanz
Copy link
Copy Markdown
Member

@sugarmanz sugarmanz commented May 22, 2026

Implement rules for testing, building, and updating Kotlin binary compatibility for player-ui/player#155.

Also:

  • moved some shared helpers around
  • introduced a top-level run target for aggregating execution of specific rules through Bazel
  • migrated to Gradle version catalog for Maven deps

Release notes

Kotlin ABI compatibility checking

New first-class support for tracking the public ABI of Kotlin libraries, mirroring JetBrains' Binary Compatibility Validator Gradle plugin.

Enabled by default on kt_jvm and kt_android. Each library gains three sibling targets that maintain a checked-in api/{name}.api golden file:

load("@rules_player//kotlin:defs.bzl", "kt_jvm")

kt_jvm(
    name = "my-lib",
    # api_file = "api/custom-name.api"   # override path
    # api_file = None                    # disable
)

Generated targets:

  • :my-lib-abi-dumpbazel build emits a canonical BCV-format .api dump
  • :my-lib-abi-checkbazel test diffs the dump against the golden, failing with a unified diff on mismatch
  • :my-lib-abi-updatebazel run regenerates the golden file in the source tree

First-time bootstrap is friction-free: if the golden file doesn't exist yet, an empty placeholder is auto-stubbed so the first abi-check produces a clean "everything is new" diff instead of a load-time error. Run :my-lib-abi-update to materialize the real file.

$ bazel test //my-lib:my-lib-abi-check
FAILED — public ABI changed
--- api/my-lib.api
+++ my-lib-abi-dump.api
@@ -2,3 +2,4 @@
 public final class com/example/Foo {
   public fun <init> ()V
+  public fun newApi (Ljava/lang/String;)V
 }

Run: bazel run //my-lib:my-lib-abi-update

Advanced filtering matches BCV's Gradle plugin — opt declarations in/out via package, class, or annotation:

kt_jvm(
    name = "my-lib",
    abi_public_packages = ["com.example.api"],
    abi_public_markers = ["com/example/PublicApi"],
    abi_non_public_packages = ["com.example.internal"],
    abi_non_public_markers = ["kotlin/RequiresOptIn"],
)

Workspace-level batch runners

Two new macros enumerate every matching target in the workspace at bazel run time and invoke each in turn — no deps list to maintain:

load("@rules_player//kotlin:defs.bzl", "abi_update_all", "lint_fix_all")

abi_update_all(name = "abi-update-all")
lint_fix_all(name = "lint-fix-all")
$ bazel run :abi-update-all
==> run //libs/auth:auth-abi-update
==> run //libs/parser:parser-abi-update
...

$ bazel run :lint-fix-all
==> run //libs/auth:auth-lint-fix
==> run //libs/parser:parser-lint-fix
...

Discovery is automatic: any newly-introduced abi_update or ktlint_fix target is picked up on the next run.

sugarmanz and others added 7 commits May 22, 2026 13:34
Introduce `abi_dump`, `abi_check_test`, and `abi_update` rules plus an `abi`
macro that wires them together for any kt_jvm_library-style target, mirroring
the JetBrains Binary Compatibility Validator Gradle plugin. The dump itself is
produced by a Clikt-based CLI (`kotlin/private/abi/AbiCli.kt`) wrapping the
BCV library's JVM API.

Wrappers `kt_jvm` and `kt_android` opt in via `api_file`; the default
`api/{name}.api` matches BCV's Gradle layout. The check rule auto-stubs an
empty placeholder when the golden file is missing so first-run bootstrap via
`abi-update-all` doesn't hit a load-time error.

`abi_update_all` is a `bazel run`-able launcher that uses `bazel cquery` to
discover every `abi_update` target in the workspace, sidestepping the
limitations of aspect-based aggregation. The nested `bazel` calls work
because `bazel run` `exec`s the launcher before the inner invocations.

Also adds `//private:maven.bzl` exposing a thin `artifact()` helper pinned
to `rules_player_maven`, and migrates existing `@rules_player_maven//:...`
references in `distribution/BUILD` to use it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the workspace-discovery shell launcher used by `abi_update_all` into
`//internal/private:run_all_of_kind.sh`. The script now takes `--kind`,
`--mode`, and `--scope` so any rule kind can be enumerated and invoked.

`abi_update_all` becomes a thin macro wrapping the shared script with
`--kind abi_update`. A new sibling macro `lint_fix_all` does the same for
`--kind ktlint_fix`, giving a single `bazel run :lint-fix-all` entry point
for workspace-wide lint fixing.

The label is re-exported as `RUN_ALL_OF_KIND` from `//internal:defs.bzl`
so consumer macros can reference it via a stable path next to the script.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promote `scope_name.bzl` from `//kotlin/private` to `//internal/private`
since it's a generic naming helper used across rule families, not Kotlin-
specific. Re-export it from `//internal:defs.bzl` so consumers load the
function through the public defs surface alongside `stamp` and
`RUN_ALL_OF_KIND`.

All five callers in `kotlin/private/` (abi, distribution, lint, and the
two library-and-test wrappers) now `load("//internal:defs.bzl",
"scope_name")`. The `bzl_library` graph follows the same path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sugarmanz sugarmanz added the minor Increment the minor version when merged label May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Increment the minor version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant