ライブラリ更新をサボったらKotlinの互換性がなくなった

この記事をシェア

この記事では、Kotlinのバイナリの互換性と、ライブラリの開発に利用するKotlinのバージョンに関する注意事項を紹介します。この記事の内容は、私が開発して公開しているZoomableというComposeのライブラリで起きた事例です。

Issue ━ ライブラリ更新で起きた問題

Zoomableは元々Androidを対象としたJetpack Composeのライブラリでしたが、2024年12月にKMPに対応しました。このとき、依存するKotlinのバージョンを1.9.22から2.1.0にアップデートしました。Kotlin 2.0を飛ばして、1.9からいきなり2.1にアップデートしたことになります。(2024年は忙しくて、2.0に更新するタイミングを逃してしまったのです)

すると、Kotlin 1.9を利用しているライブラリユーザーから、ライブラリを使えなくなったというIssueの報告を受けました。下記のエラーが大量に出ているということでした。

Class 'kotlin.Unit' was compiled with an incompatible version of Kotlin. The actual metadata version is 2.1.0, but the compiler version 1.9.0 can read versions up to 2.0.0.
Class 'net.engawapg.lib.zoomable.ZoomState' was compiled with an incompatible version of Kotlin. The actual metadata version is 2.1.0, but the compiler version 1.9.0 can read versions up to 2.0.0.

原因 ━ Kotlinバイナリの互換性

調べていくと、Kotlinのバージョン間の互換性に原因があることがわかりました。

Evolving the binary format – kotlinlang.org」によると、Kotlinのバイナリは1つ先のバージョンのバイナリとは互換性があるが、2つ先とは互換性がない(保証されていない)とのことでした。つまり、Kotlin 1.9を利用してアプリをビルドする場合、2.0でビルドしたライブラリは利用できるが、2.1でビルドしたライブラリは利用できないということになります。

Kotlin evolution principles – kotlinlang.org」によると、Kotlinは思想として常にモダンであることを重視しています。モダンであり続けるために、新しい機能の追加だけでなく、古い機能の削除も大切だと書かれています。バイナリの互換性が1世代だけに限定されているのも、この思想によるのだろうと思います。

反省 ━ Kotlinバージョンのスキップ

今回よくなかったのは、ライブラリの更新をサボっていたことで、Kotlin 2.0をスキップしてしまったことです。Kotlin 2.0であれば1.9とも互換性がありました。しかし、いきなり2.1にアップデートしてしまったため、1.9を使っているユーザーは急にライブラリが使えなくなってしまったことになります。ライブラリ開発者として、Kotlinのバージョンを飛ばすべきではありませんでした。

回避策 ━ 3つのコンパイラオプション

モダンであることを大切にしているKotlinですが、古いバージョンで動かすための方法は用意されています。以下のコンパイラオプションを指定することで、古いKotlinで使えるようになります。

  • languageVersion:指定したバージョンのKotlinの言語機能のみを使う
  • apiVersion:依存ライブラリ(stdlibなど)の指定したバージョンのAPIのみを使う
  • coreLibrariesVersion:指定したバージョンのcoreライブラリ(stdlibなど)への依存を追加する

ライブラリ開発の場合は以下のようになると思われます。

  • languageVersionapiVersionを指定することで、ライブラリのソースコードで利用できるKotlinの機能やAPIが決まる
  • coreLibrariesVersionを指定することで、ライブラリのアーティファクトに含まれる依存ライブラリのバージョンが決まる

次のようにbuild.gradle.ktsに記述すると、Kotlin 2.1のコンパイラを利用しても、Kotlin 2.0相当のライブラリをビルドできます。Kotlin 2.0は1.9と互換性があるので、Kotlin 1.9でも利用できるライブラリをビルドできます。

compilerOptions {
    languageVersion = KotlinVersion.KOTLIN_2_0
    apiVersion = KotlinVersion.KOTLIN_2_0
}
coreLibrariesVersion = "2.0.21"

方針 ━ 特別バージョンのリリース

今回の件を調べるにあたって、「Conservative libraries with liberal tooling – blog.allex.me」を参考にしました。この記事では、最新のツールの恩恵を受けつつライブラリを多くの人に使ってもらうために、上記のオプションに古いバージョンを指定することを推奨していました。

ただ、私としては、個人開発ライブラリを継続的にメンテナンスしていくために、ライブラリやツールの依存関係はなるべくシンプルにしておきたいと考えました。そのため、mainブランチでは最新のライブラリやツールを素直に使う形にしたいと考えました。

一方で、私がライブラリのメンテナンスをサボってKotlinのバージョンをスキップしてしまったために、KMP対応したZoomableを使えない人が出てしまうのは残念でもありました。

そこで今回は、Kotlin 2.0と1.9で利用できる特別バージョンをリリースし、mainブランチは最新の環境を保つことにしました。

教訓

ライブラリのメンテナンスはサボらないようにしましょう。

この記事をシェア