Kotlin/Nativeは現在
- Android NDK(androidNativeArm32, androidNativeArm64)
- Darwin
- macOS(macosX64, macosArm64)
- iOS(iosArm32, iosArm64, iosX64, iosSimulatorArm64)
- watchOS(watchosArm32, watchosArm64, watchosX86, watchosX64, watchosSimulatorArm64)
- tvOS(tvosArm64, tvosX64, tvosSimulatorArm64)
- Linux(linuxArm64, linuxArm32Hfp, linuxMips32, linuxMipsel32, linuxX64)
- Windows(mingwX64, mingwX86)
- wasm(将来deprecated)
のプラットフォームに対応しています。
1.5.30からApple siliconの正式サポートもされました🎉
Kotlin MultiPlatform向けに作ってるログライブラリのNapier
もAndroid, JVM, JS, iOS, watchOS, tvOS, macOS[各Intel, Apple Silicon] をサポートしているのですが、普段はMacで開発しているので他のプラットフォームでデバッグするための環境を用意するのが面倒です。
MacなのでAndroid, JVM, JS, DarwinまではいけるのですがKotlin/NativeのLinux, Windows(今回は触れない)を実行できません。
そこでこの記事では、Kotlin/NativeでLinux用に生成したBinaryをDocker上で実行する方法について書きます。
動作環境
OSのディストリビューションはなんでも良いのですがUbuntuのイメージ を使います
ubuntu:latest
のタグはデフォルトでは amd64(x86_64)
だったのですが、念のためイメージは amd64/ubuntu:latest
を指定しておきます。
アーティファクト生成
gradleファイルにlinuxX64を指定します
plugins {
kotlin("multiplatform")
}
kotlin {
linuxX64 {
binaries {
sharedLib {
baseName = "native"
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
}
}
val nativeMain by creating {
dependsOn(commonMain)
}
val linuxX64Main by getting {
dependsOn(nativeMain)
}
}
}
binariesには 動的ライブラリ(*.so)を生成する sharedLib
を指定しています。
他にも 静的ライブラリ(*.a)を生成する staticLib
, 実行可能ファイルを生成する executable
などがあります。[Kotlin/Native Declare binaries]
Kotlin MultiPlatformのGradleの書き方は色々コツがあります。
今回の場合は LinuxX64
のみ対応しますが、例えば Linux64
以外にも linuxArm64
や iosArm32
などの同じKotlin/Nativeのコードを一括で書く場合は、
nativeMain
のように適当な名前をつけて dependsOn
で依存をまとめると楽です。
creatingした nativeMain
のMainより前がディレクトリ名になります。
例えばNapierのディレクトリ構成はこのようになっています。
.
├── build.gradle.kts
├── gradle
└── src
├── androidMain
├── commonMain
│ └── kotlin
│ └── io
│ └── github
│ └── aakira
│ └── napier
│ ├── Antilog.kt
│ ├── DebugAntilog.kt
│ ├── LogLevel.kt
│ ├── Napier.kt
│ └── atomic
│ ├── AtomicMutableList.kt
│ └── AtomicRef.kt
├── darwinMain(iOS, watchOS, tvOS, macOS)
├── jsMain
├── jvmMain
└── nativeMain
└── kotlin
└── io
└── github
└── aakira
└── napier
├── DebugAntilog.kt
└── atomic
└── AtomicRef.kt
これで準備はできました。
./gradlew task
を実行すれば linuxX64に関するタスクが生成されていると思います。
コード
Kotlin MultiPlatform
Kotlinで書かれた共通部分のコードはこのようになっています。
cf. Napierのmpp-sampleディレクトリ
class Sample {
fun hello(): String {
Napier.v("Hello napier")
Napier.d("optional tag", tag = "your tag")
return "Hello Napier"
}
suspend fun suspendHello(): String {
Napier.i("Hello")
delay(3000L)
Napier.w("Napier!")
return "Suspend Hello Napier"
}
fun handleError() {
try {
throw Exception("throw error")
} catch (e: Exception) {
Napier.e("Napier Error", e)
}
}
}
(Kotlin部分の詳しい説明はしません)
MultiPlatform部分の準備ができたら linkDebugSharedLinuxX64
のタスクを実行します。
./gradlew linkDebugSharedLinuxX64
タスクが成功すると /build/bin/linuxX64/debugShared/
ディレクトリ配下に
libnative.so
libnative_api.h
が生成されています。
native部分はgradleのbaseName
に指定した名前になります。なにも指定しない場合はプロジェクト(ディレクトリ)名になります。
C言語
先程生成されたheaderファイルはこのようになっています。
...
typedef struct {
...
/* User functions. */
struct {
struct {
struct {
struct {
struct {
struct {
struct {
void (*debugBuild)();
void (*suspendHelloKt)(libnative_kref_io_github_aakira_napier_mppsample_Sample thiz);
struct {
libnative_KType* (*_type)(void);
libnative_kref_io_github_aakira_napier_mppsample_Sample (*Sample)();
void (*handleError)(libnative_kref_io_github_aakira_napier_mppsample_Sample thiz);
const char* (*hello)(libnative_kref_io_github_aakira_napier_mppsample_Sample thiz);
} Sample;
} mppsample;
} napier;
} aakira;
} github;
} io;
} root;
} kotlin;
} libnative_ExportedSymbols;
extern libnative_ExportedSymbols* libnative_symbols(void);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* KONAN_LIBNATIVE_H */
生成されたライブラリをincludeしてコードを書きます。
パッケージ名を指定するので少し長くなってしまいますが他の言語と同様に、問題なく利用できます。
#include <stdio.h>
#include "libnative_api.h"
int main(int argc, char **argv) {
// init kotlin native
libnative_ExportedSymbols* lib = libnative_symbols();
// init napier
lib->kotlin.root.io.github.aakira.napier.mppsample.debugBuild();
// run napier
libnative_kref_io_github_aakira_napier_mppsample_Sample newInstance = lib->kotlin.root.io.github.aakira.napier.mppsample.Sample.Sample();
lib->kotlin.root.io.github.aakira.napier.mppsample.Sample.hello(newInstance);
// handleError
lib->kotlin.root.io.github.aakira.napier.mppsample.Sample.handleError(newInstance);
// suspend function
lib->kotlin.root.io.github.aakira.napier.mppsample.suspendHelloKt(newInstance);
// dispose
lib->DisposeStablePointer(newInstance.pinned);
return 0;
}
Dockerで動かす
実行用ShellScript
Docker内でコンパイルして実行するShellScriptを作っておきます。
#!/bin/bash
cd /work && \
gcc napier.c -o napier -Wl,-rpath ./ -L. -lnative && \
./napier
Kotlin側で生成したビルドファイルと生成したShellScriptを任意のディレクトリにコピーしておきます。(ここでは/work
)
cp -r /build/bin/linuxX64/debugShared/ /work
DockerFileでは gcc
をインストールします。
FROM amd64/ubuntu:latest
RUN apt-get update && \
apt-get -y install gcc && \
mkdir work
COPY /work /work
CMD /work/build.sh
あとは実行するだけです。
docker build -t napier .
docker run napie
todo time 💜 VERBOSE todo - Hello napier
todo time 💚 DEBUG your tag - optional tag
todo time ❤️ ERROR todo - Napier Error
...
まとめ
Gradleの設定さえできれば、Dockerでも簡単にKotlin/Nativeのライブラリを読み込んで実行できました。
Flutterも便利ですが、UIより下のレイヤーではKotlin MultiPlatform Projectの方が扱いやすいかなと思います。
Help! (2021/08/30)
Napier
というログのライブラリでLinuxを対応したいのですが、
Kotlin/Naitve(Linux, Windows)でbacktraceの取得方法がわかりませんでした。linux
branchを用意してあるので、もし知っていたら それを使って(参考にして)プルリクくれると嬉しいです!!!!!!!