👻

Jetpack ComposeをLinuxデスクトップで動作させる

7 min read

Jetpack ComposeをLinuxデスクトップで動作させる

12/8追記: Jetpack Compose for Desktop M1が11月にリリースされたのですが、この記事は10月時点での情報をもとに書かれているので、今はここまで手作業をがんばらなくてもできます。IntelliJ IDEAで用意されたプロジェクトテンプレートを使って新規作成してGradleタスクを実行するだけです。公式ページを見てください。

Jetpack ComposeはGoogleが公式に移行を目指しているAndroidの次世代ネイティブUIフレームワークです。その詳しい技術的背景については1年くらい前に同人誌の記事に書いたので興味のある人は見てもらえればと思いますが、既存のAndroidフレームワークへの依存は極小に抑えてあり、Android以外のプラットフォームでの利用可能性がある設計になっています。

それが今年の4月に、具体的にデスクトップ上でも動作可能なコードとしてAOSPにチェックインされました。これはこの方面に関心のある開発者の間でそれなりに話題になり、実際にJetpack Compose Desktopを試す人も出てきました。たとえばこの記事ではUbuntu 20.04上でビルドして実行する方法がまとめられています。

https://zeromod.in/posts/jetpack-compose-desktop/

ただしこの記事はもう古くて役に立ちません。半年経って、リポジトリの構成も依存パッケージの状況も変わってきたためです。この記事では最新のAOSPに基づいてUbuntu 20.04上でJetpack Composeを試す方法をまとめます。ちなみにdesktopビルドはドキュメント上はMacOSのみサポートと書かれていますが、実際にはLinuxでもそこそこ動きます。

https://twitter.com/atsushieno/status/1321377093901447169

Jetpack Compose Desktop on Ubuntu 20.04 (2020.10.29)

Jetpack Compose Androidのチェックアウトとビルド

Jetpack ComposeはAOSP上ではplatform/frameworks/supportのリポジトリ上に存在しているので、主にこの内容を調べることになりますが、まずはJetpack Composeを公式のAndroid用のやり方でチェックアウトしてビルドしてみるのがよいでしょう(デスクトップ用の公式なビルド手順はありません)。公式のやり方はplatform/frameworks/supportのREADMEにまとめられています。

https://cs.android.com/androidx/platform/frameworks/support

ANDROIDX_PROJECTS=COMPOSE ./gradlew studio のビルドが成功すると、最後にビルド済みのAndroid Studioを立ち上げた上でコンソールに制御が戻ってきます。その独自Android Studio上でcompose.integration-tests.demosをデバッグ実行すると、現状のJetpack Composeのデモが動作確認できるでしょう(SDKも独自にダウンロードしてくる、ただしエミュレーターを一切含まない剛毅なスクリプトなので、Androidデバイスを繋ぐなり別途emulatorをダウンロードするなりして対処しましょう)。

Jetpack Compose Desktopのビルドと実行

Jetpack Compose Androidが動作確認できたらいよいよDesktopを試してみましょう。この記事ではUbuntu 20.04で動作確認していますが、MacOSではもしかしたらもう少し親切な手順があるかもしれませんし、Windowsは全くサポートされていないかもしれません。

デスクトップ版で注意すべき点は、Jetpack Compose DesktopはKotlin MPP (multiplatform project) を利用しているという点です。ビルドされる依存関係ライブラリは、デスクトップ版がある場合にはそちらを参照する必要があります。また、最新のKotlin 1.4.xを使うのでstdlib参照などもそちらに統一します。確認まではしていませんが、Jetpack Composeはkotlincの最新(しかも未完成)の機能である新しいコンパイラバックエンドのIRを前提とするKotlin Compiler Pluginの機能を利用するので、1.4系列は必須になるでしょう。

まあ、今回提示する手順ではどのライブラリjarを参照する必要があるかはわたしが調べているので、単に試してみたいだけの人は気にする必要はありません。

Android Studioからはビルドできません。そもそもAndroidアプリケーションではないですし。IDEAとして設定を調整すればワンチャンあるかもしれませんがわたしは知りません。

desktop版のビルドには以下のコマンドが使えます。もっと細かく最小限のビルドを指定できる可能性はありますがわたしは知りません。frameworks/supportディレクトリで実行してください。

ANDROIDX_PROJECTS=COMPOSE ./gradlew -Pandroidx.compose.multiplatformEnabled=true :compose:desktop:desktop:desktop-samples:build

ビルドされるのはJavaアプリケーションです(Kotlin Nativeのネイティブアプリケーションなどではありません)。ビルド結果は $(topdir)/outディレクトリ上に出力されます。platform/framework/support以下ではありません。

サンプルアプリケーションの実体は ./out/androidx/compose/desktop/desktop/desktop-samples/build/libs/desktop-samples-jvm.jarにあります。ただし実行可能なjarではありません。javaコマンドでclassを指定して実行する必要があります。このために必要な依存関係を調べるのが面倒でしたが、わたしは以下のコマンドラインで実行しています。依存していそうなライブラリを含むディレクトリからfindで適当にjarを列挙して-cp引数に指定しているだけなので、実際には必要のないライブラリが含まれている可能性もありますが、いちいち調べてはいません。多分ほとんど必要だと思います。

java -cp \
./prebuilts/androidx/external/org/jetbrains/kotlinx/kotlinx-collections-immutable-jvm/0.3/kotlinx-collections-immutable-jvm-0.3.jar\
:./prebuilts/androidx/external/org/jetbrains/kotlinx/kotlinx-coroutines-swing/1.3.9/kotlinx-coroutines-swing-1.3.9.jar\
:./prebuilts/androidx/external/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.3.9/kotlinx-coroutines-debug-1.3.9.jar\
:./prebuilts/androidx/external/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.3.9/kotlinx-coroutines-core-jvm-1.3.9.jar\
:./prebuilts/androidx/external/org/jetbrains/skiko/skiko-jvm-runtime-linux/0.1.9/skiko-jvm-runtime-linux-0.1.9.jar\
:./prebuilts/androidx/external/org/jetbrains/kotlin/kotlin-stdlib-common/1.4.0/kotlin-stdlib-common-1.4.0.jar\
:./prebuilts/androidx/external/org/jetbrains/kotlin/kotlin-stdlib/1.4.10/kotlin-stdlib-1.4.10.jar\
:out/androidx/compose/foundation/foundation-layout/build/libs/foundation-layout-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/foundation/foundation/build/libs/foundation-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/foundation/foundation-text/build/libs/foundation-text-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/foundation/foundation-lay:out/build/libs/foundation-layout-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/animation/animation/build/libs/animation-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/animation/animation-core/build/libs/animation-core-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/runtime/runtime-dispatch/build/libs/runtime-dispatch-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/runtime/runtime/build/libs/runtime-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/runtime/runtime-saved-instance-state/build/libs/runtime-saved-instance-state-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/ui/ui-unit/build/libs/ui-unit-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/ui/ui-util/build/libs/ui-util-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/ui/ui-geometry/build/libs/ui-geometry-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/ui/ui-text/build/libs/ui-text-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/ui/ui/build/libs/ui-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/ui/ui-graphics/build/libs/ui-graphics-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/material/material-icons-core/build/libs/material-icons-core-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/material/material/build/libs/material-desktop-1.0.0-alpha07.jar\
:out/androidx/compose/desktop/desktop/build/libs/desktop-jvm.jar\
:out/androidx/compose/desktop/desktop/desktop-samples/build/libs/desktop-samples-metadata.jar\
:out/androidx/compose/desktop/desktop/desktop-samples/build/libs/desktop-samples-jvm.jar\
 androidx/compose/desktop/examples/example1/MainKt

ちなみに動作するのはexample1example2だけです。代わりにpopupexampleにすると次のようなエラーになります。

onAppStart
Exception in thread "main" java.lang.UnsupportedOperationException: Taskbar API is not supported on the current platform
	at java.desktop/java.awt.Taskbar.getTaskbar(Taskbar.java:216)
	at androidx.compose.desktop.AppWindow.setIcon(AppWindow.kt:187)
	at androidx.compose.desktop.AppWindow.<init>(AppWindow.kt:150)
	at androidx.compose.desktop.AppWindow.<init>(AppWindow.kt)
	at androidx.compose.desktop.AppWindow.<init>(AppWindow.kt:136)
	at androidx.compose.desktop.examples.popupexample.MainKt.main(Main.kt:43)
	at androidx.compose.desktop.examples.popupexample.MainKt.main(Main.kt)
onAppExit

Javaのawtのレベルでサポートされていない機能に依存していることがわかります。わたしの環境ではjava --versionを実行すると14.0.1と返ってくるのでJava 14のawtで未実装なんだと思いますが正確なことはわかりません。