Slide 1

Slide 1 text

Building Kotlin Multiplatform Libraries in 2024 2024年に公開するに相応しい Kotlin Multiplatformライブラリを構築する

Slide 2

Slide 2 text

What is this session about? KMP (Kotlin Multiplatform) ライブラリを開発する時に気に留めておくとよいことをいろ いろ話します 主なトピック: - KMPライブラリプロジェクトとモジュールの作り方 - 後方互換性 - Mavenパッケージの発行 - その他やっておくとよいこと

Slide 3

Slide 3 text

whoami? Kotlin libs created so far atsushieno/ktmidi also slides at Kotlin愛好会#49, Foojay podcast #54 (6/24?) atsushieno/compose-audio-controls atsushieno/missing-dot (equivalents to .NET XML Reader/Writer, XLinq) sporadic contributions to antlr-kotlin, accompanist, nj2k (idea) etc...

Slide 4

Slide 4 text

ライブラリを作るってすごい? そんなことはないのでみんなカジュアルにやろう 再利用性のあるコードのモジュールを作って Mavenパッケージを発行するだけ(!) 最初から完璧を目指さなくてもいい 言語コミュニティの魅力はみんながライブラリでエコシステムを拡大できること

Slide 5

Slide 5 text

なぜMultiplatformでライブラリを作るの? ● これまで対象外としていたプラットフォーム向けにアプリを出せる ○ Google at I/O '24: ● ライブラリのユーザー「これに依存したらターゲットが制限されるかも…」 ○ XMLを読み書きするライブラリが JVM/JSの実装しかなかった (2021) ○ ComposeアプリをKMP化しようとしたらUIライブラリがAndroid専用だった (2024) ○ Compose MPPアプリでwasm対応しようとしたらktor-ioが…(2024)

Slide 6

Slide 6 text

より良いライブラリを作るために ● 可用性 (availability) を高める ○ 幅広いターゲットをサポートする ○ 最新バージョンの言語やツールチェインでも使えるようにする ○ Mavenパッケージを発行する ● 適切な粒度の公開APIがある ○ 以前のバージョンとの 後方互換性を維持できる ● APIドキュメントを公開/配布する even better: ネイティブ プラットフォーム言語でも使える =「実装言語としてのKotlin」

Slide 7

Slide 7 text

Library authors' guidelines (Kotlin公式) 最近アップデートされた (2024.6) https://kotlinlang.org/docs/api-guidelines-introduction.html 今日のトピックと内容がそこそこかぶる(!)

Slide 8

Slide 8 text

Create New Project

Slide 9

Slide 9 text

ライブラリプロジェクトのテンプレート https://kmp.jetbrains.com で作る Multiplatform Libraryテンプレートが便利 Maven設定も含まれていて便利

Slide 10

Slide 10 text

KMPのモジュール構成 KMP Project Wizardが作る構成がよくできている 従来  common, android, desktopなどが別々のモジュール 2024年  1つのモジュールで1つのKMPライブラリを構築できる 参照解決も project(":myLibrary") だけで可能 (Gradleプラグイン上での実現は単純ではない)

Slide 11

Slide 11 text

Multiplatformのターゲット 2024: 幅広いtarget ● jvm (desktop), androidTarget ● js (IR) ● native (desktop, ios*, android native(!) ● wasmJs (alpha) ※最近のトレンド ターゲットを増やすとどうなる ? ● common APIの制約に縛られる ● ビルド時間が余計にかかる ターゲットを減らすとどうなる ? ● 可用性が低くなり使ってもらえなくなる (!) どのターゲットをビルド対象にするかは開発者次第 (build.gradle.ktsで明示的に指定する) android nativeも一部のライブラリでは対応(kotlinx.datetimeなど)

Slide 12

Slide 12 text

Mavenパッケージとしてローカル発行する Kotlinライブラリは通常はMavenパッケージとして配布/参照する maven-publish Gradleプラグインを使う ./gradlew (build) publishToMavenLocal で mavenLocal リポジトリに発行 ➡ 他のプロジェクトでも参照できるようになる  (repositories { mavenLocal() } が必要) mavenLocalにあるパッケージの内容は以下のようなパスに格納される : $HOME/.m2/repository/dev/atsushieno/ktmidi-*/0.1.0/

Slide 13

Slide 13 text

Backward Compatibility

Slide 14

Slide 14 text

後方互換性(の問題)の種類 ● APIの互換性 - 損なわれるとビルド時エラーになる ● ABIの互換性 - 損なわれると実行時エラーになる ● 挙動の互換性 - 損なわれると想定外の挙動になる 挙動の互換性はテストで担保する(しかない) ABIの互換性維持がとにかく重要 ABIを損なわないAPI互換性破壊は(相対的に)大した問題ではない

Slide 15

Slide 15 text

ABI互換性を維持するには? Q: 何を変更したらABI互換性が損なわれる? A: いろいろある… 例: 「data classを使うな」「関数の引数を増やすな」 Library authors' guidelines(前出)が規範的情報源 ABI互換性チェックは ./gradlew check に組み込むと安心 Kotlin/binary-compatibility-validator (metalava-gradleなども使えるが、Kotlin生成コードで偽陽性を検出することもある)

Slide 16

Slide 16 text

Kotlin 2.0に移行する? [1/3] Q: K2でビルドしたライブラリはK2のプロジェクトでないと使えない? A: 理論上はnoだが、現実にはyesかもしれない 例: kotlinx.serialization: 1.7.0-RCはserialization plugin 2.0.0-RC1以降を要求する (Gradleプラグインがエラーとして報告する) 回避?策 : serialization pluginだけkotlin 1.9.xを使う(ビルドは通るが安全性は不明 !)

Slide 17

Slide 17 text

Kotlin 2.0に移行する? [2/3] 例: K1でK2なライブラリを参照するとwasmJsビルドでコンパイラがクラッシュしうる (wasmJsターゲットは1.9.xではpreview機能だから想定の範囲内) ➡ 利用する側が1.9.xでビルドできないなら、K2の波に乗ってもらうしか無い(!)

Slide 18

Slide 18 text

Kotlin 2.0に移行する? [3/3] Q: ライブラリのサポート範囲を最大化するならKotlin 1.9.xに留まり続けるべき? A: Gradleプラグインが要求してこなければたぶん可能…?  Compose compiler, Serialization, KSP... コンパイラとstdlibとコンパイラプラグインのバージョンが統一されないといけないうちは たぶん解決できない問題

Slide 19

Slide 19 text

補遺: 依存ライブラリのバージョン選定 [1/3] Q: 依存関係解決時に同じライブラリの複数ver.を参照されたら、どう解決する? foo:libraryA:1.0.0 foo:libraryA:1.1.0 bar:libraryB:1.0.0 baz:libraryC:1.0.0 MyApplication which one?😱

Slide 20

Slide 20 text

補遺: 依存ライブラリのバージョン選定 [2/3] A: 依存関係の距離が「近い」ほうが選ばれる🙃  Mavenの仕様なので仕方ない 問題が生じたら、ユーザーは望ましいバージョンのほうを「近づける」  (build.gradle(.kts) で明示的な dependencies や resolutionStrategy を記述する)

Slide 21

Slide 21 text

補遺: 依存ライブラリのバージョン選定 [3/3] Q: 古いほうと新しいほう、参照するならどっちが望ましい? A: 一般的には新しいほうでよい(バグフィックス、パフォーマンス向上など)  大抵はsemantic versioningがきちんと機能していると期待できる(!?) ● 場合によっては参照可能なバージョン範囲を指定しないといけないこともある Gradleには実際にはバージョン範囲を指定する方法が( mvnと同様に)存在する ただ、誰も使ってない (!?) ● Renovateによる自動化もトレンドだが、 AGPなどIDEAの事情で上げられないものを考慮しない、 Composeの要求バージョンとの相性調整が必要など、運用にはまだ覚悟が必要そう

Slide 22

Slide 22 text

Publish

Slide 23

Slide 23 text

Mavenパッケージ発行までのワークフロー ./gradlew build publish でリモートリポジトリに発行できる CI経由でビルドの再現性・透明性・安全性を担保したほうが妥当 (CircleCI, Bitrise, GitHub Actions, local Jenkins, ...) 2024年: mac/ios nativeターゲットはクロスコンパイルできないので macosビルドが必要… cf. youtrack KT-52666, KT-68323, KT-66944 2024年: Maven Centralへの発行が望ましい ● jitpack: 自前ビルドするしpublishすら不要でこの上なく楽だけど、 mac/ios nativeが未サポート… ● GitHub PackagesはDLにユーザー認証が必須なのが致命的 ● jcenter (bintray)は発行受付停止

Slide 24

Slide 24 text

Sonatype Nexus [1/3] Maven Centralには承認されたサービス経由で 要件を満たすパッケージのみ発行できる ● 開発者の電子署名があること ● sourcesJar, javadocJarがあること ● 十分なパッケージメタデータがあること etc. etc. ➡ Sonatype Nexusが唯一的(?)な選択肢 日本語解説もいくつかある https://aakira.app/blog/2019/03/upload-kotlin-mpp-library/ (bintray時代) https://qiita.com/arashiyama/items/0c997126fb6ee0f9c8e7 Developer invalid package valid package Maven Central Package Publisher Agent

Slide 25

Slide 25 text

Sonatype Nexus [2/3] ⚠2024年2月以降の新規SonatypeユーザーはPortal Publisher APIの利用が必須 Sonatypeの正式なGradleサポートがまだ無い (!) https://central.sonatype.org/publish/publish-portal-gradle/ 非公式のコミュニティ Gradleプラグインで対処するしかない( ↑で列挙あり) ⚠2024: ユーザー認証にSonatypeアカウント名を使わなくなった(移行期間中) 最新の仕組みではMavenユーザー名とパスワードを access tokenのように生成して使う

Slide 26

Slide 26 text

Sonatype Nexus [3/3] KMP Project WizardのMaven設定は2023年11月の古い設計なので利用できない (!) のでPublisher Portal API対応版のforkを作った https://github.com/atsushieno/multiplatform-library-template-nexus-publisher 解説記事: https://zenn.dev/atsushieno/articles/d066e757c9640f

Slide 27

Slide 27 text

Going Further

Slide 28

Slide 28 text

APIリファレンスを発行する KDocを書いてDokkaでAPIリファレンスを発行する KMP Project WizardテンプレートではDokkaドキュメントが取り込まれないが、 DokkaJarを javadocJarとして認識させて提供したほうが良いしその方法もある https://github.com/gradle/gradle/issues/26091#issuecomment-1681343496 IDEAはDokkaJarのKDocを認識してコード補完等で表示してくれる GitHub Actions:github-pages-deploy-actionも有用

Slide 29

Slide 29 text

Kotlinエコシステムの「外側」へ 各ターゲット「で」ネイティブ(?)ライブラリとして利用したい ● .dll/.lib/.so/.a/.framework/.js/.wasm ● npmにしてvscode拡張で参照したい やり方: build.gradle(.kts) の binaries.* メソッドを呼ぶ ● js, wasm: binaries.executable(), binaries.library() ● native: binaries.executable(), binaries.staticLib(), binaries.sharedLib() ○ mac, ios: binaries.framework { ... }

Slide 30

Slide 30 text

補遺: 複数のCIビルドの生成物を整合して発行する ● 複数のCIホストからのビルドアーティファクトが必要になる場合がある ○ mac/ios nativeターゲットはクロスコンパイルできない(macosホストを使えば解決) ○ nativeターゲットをビルドするとき、cinteropで外部ライブラリもビルドしてリンクしていると、プラットフォームごとにビル ドしなければならなくなる ● ./gradlew publish では別々のNexusリポジトリがホスト分だけ生成される ➡ パッケージ発行時に競合が生じて失敗する ● Sonatype NexusではWeb APIでリポジトリ作成と送信を別々に実行できるが、 2024年のGitHub Actionsではsecret (password) をもとに生成された変数の受け渡しができない 解決策 ➡ ホストごとに publishターゲットを細かく指定して重複しないように調整 … run: ./gradlew iosArm64Binaries packageDmg publishMacosArm64PublicationToOSSRHRepository PublishIosArm64PublicationToOSSRHRepository PublishIosSimulatorArm64PublicationToOSSRHRepository

Slide 31

Slide 31 text

NPMにKotlin/JSパッケージを発行 npm-publish Gradleプラグインを使う ./gradlew publish でnpmにも発行される ⚠Sonatype Nexusで失敗してもnpmで発行済のパッケージは取り消せない(逆も同じ)

Slide 32

Slide 32 text

作ったKMPライブラリを宣伝する KMPプロジェクトを作成したらここに載せて !と言われている(Kotlin公式ドキュメント) https://libs.kmp.icerock.dev/ Kotlin Foundation Grants (6/25まで) https://kotlinfoundation.org/grants/

Slide 33

Slide 33 text

Lastly - みんなでカジュアルにライブラリを作ってエコシステムを育てていきましょう❢