こんな感じ。stdin も渡した方がいいね
// ./gradlew :capjoy:runDebug -PexecArgs="displays"
tasks.register<Exec>("runDebug") {
dependsOn("linkDebugExecutableNative")
val arguments = project.findProperty("execArgs")?.toString()?.split(" ") ?: listOf()
args(arguments)
environment("CAPJOY_GRADLE_RUN_DEBUG", "true")
executable = file("$buildDir/bin/native/debugExecutable/capjoy.kexe").absolutePath
standardInput = System.`in`
}
最初にkotlin native を動かした場合
e: org.jetbrains.kotlin.konan.MissingXcodeException: An error occurred during an xcrun execution. Make sure that Xcode and its command line tools are properly installed.
Failed command: /usr/bin/xcrun xcodebuild -version
Try running this command in Terminal and fix the errors by making Xcode (and its command line tools) configuration correct.
というようなエラーになることがあります。
sudo xcode-select --switch /Applications/Xcode.app
この場合はこうすればOK.
build.gradle.kts を以下のようにする。
plugins {
kotlin("multiplatform") version "2.0.0"
}
group = "org.example"
version = "1.0-SNAPSHOT"
kotlin {
macosArm64("native") {
binaries {
executable()
}
}
sourceSets {
}
}
repositories {
mavenCentral()
}
ソースファイルを src/nativeMain/kotlin/Main.kt
あたりに置いておけばOK。
Hello, world レベルのビルドが、6秒ぐらい。
コマンドの実行自体は速い。
https://v2.tauri.app/start/migrate/from-tauri-1/#migrate-to-tray-icon-module
v2 だと SystemTray は TrayIcon に変わっている。
マイグレーションガイドの通りにやれば、動きそう。
僕が neojot で tauri v2 beta を使い始めた頃には、ドキュメントが皆無だったが、最近は migration guide が充実しているので良い。
kotlin 公式で power-assert が出てた。これは最高っぽい。
(まだできたてホヤホヤ Experimental です)
実際動かしてみて、その結果をレポジトリに置いておいた。
https://github.com/tokuhirom/kotlin-power-assert-demo
assertj/assertK などのアサーションライブラリは便利なのだのだけど、、
assertion が失敗したときに、どの処理で失敗したかとかがわかりにくい
結果的に、デバッガで追わないとよくわからんということになりがち。
それを避けるために、assertion を段階的に書けたりして、冗長になった
そこで、power-assert ですよ。
assertK でこう書いてたコードが
val people = listOf(Person(name = "Sue"), Person(name = "Bob"))
assertThat(people)
.extracting(Person::name)
.containsExactly("Sue", "Bob")
こう書いたら
@Test
fun testComplex() {
val people = listOf(Person("Sue"), Person("Bob"))
assert(people.map { it.name } == listOf("Susie", "Bob"))
}
こうだ!
Assertion failed
assert(people.map { it.name } == listOf("Susie", "Bob"))
| | | |
| | | [Susie, Bob]
| | false
| [Sue, Bob]
[Person(name=Sue), Person(name=Bob)]
最高!
なお、multiplatform にも対応しているみたい。
とくに、kotlin/js とかでテスト書く時にはコレ使うと便利そうねぇ。
(kotlinx.test は便利だがマッチャの種類が少なめ。そこをこれでカバーできそう。 )
https://tauri.app/v1/guides/features/icons/
これで解決。簡単。
npm run tauri icon ~/Documents/neojot.png
kotlinx.serialization は、kotlin 用のシリアライゼーションライブラリである。
gradle のプラグインが提供されていて、@Serializable アノテーションがついているクラスに対するシリアライザを生成してくれる。
プラグインのソースコードは https://github.com/JetBrains/kotlin/tree/master/plugins/kotlinx-serialization にある。なにかコード生成ライブラリ使ってるのかと思ったら kotlin そのものの機能を使ってそう。
kotlinx.serialization のドキュメントは以下の場所にある。Polymorphism のような機能があって Jackson に出来ることは一通りできそう。
https://github.com/Kotlin/kotlinx.serialization/blob/4bf4113c2c6936e841caf2a8670382e701e7fcc2/docs/serialization-guide.md
たとえば以下のように使うのだが、、
package org.example
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class Greeting(val greeting: String)
fun main() {
println("Hello World!")
println(Json.encodeToString(Greeting("Hello, Kotlin/JS!")))
}
生成されるコードは以下のようになっている。Greeting$serializer.serialize
が生成されてるので、実行時には高速に処理ができそうだ。
(kotlin のデコンパイルを IDEA がサポートしてほしいなぁと思いつつ幾星霜)
// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available
package org.example
@kotlinx.serialization.Serializable public final data class Greeting public constructor(greeting: kotlin.String) {
public companion object {
public final fun serializer(): kotlinx.serialization.KSerializer<org.example.Greeting> { /* compiled code */ }
}
internal constructor(seen0: kotlin.Int, greeting: kotlin.String?, serializationConstructorMarker: kotlinx.serialization.internal.SerializationConstructorMarker?) { /* compiled code */ }
public final val greeting: kotlin.String /* compiled code */
public final operator fun component1(): kotlin.String { /* compiled code */ }
public open operator fun equals(other: kotlin.Any?): kotlin.Boolean { /* compiled code */ }
public open fun hashCode(): kotlin.Int { /* compiled code */ }
public open fun toString(): kotlin.String { /* compiled code */ }
@kotlin.jvm.JvmStatic internal final fun `write$Self`(self: org.example.Greeting, output: kotlinx.serialization.encoding.CompositeEncoder, serialDesc: kotlinx.serialization.descriptors.SerialDescriptor): kotlin.Unit { /* compiled code */ }
@kotlin.Deprecated public object `$serializer` : kotlinx.serialization.internal.GeneratedSerializer<org.example.Greeting> {
public final val descriptor: kotlinx.serialization.descriptors.SerialDescriptor /* compiled code */
public final fun childSerializers(): kotlin.Array<kotlinx.serialization.KSerializer<*>> { /* compiled code */ }
public final fun deserialize(decoder: kotlinx.serialization.encoding.Decoder): org.example.Greeting { /* compiled code */ }
public final fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: org.example.Greeting): kotlin.Unit { /* compiled code */ }
}
}
さて、実際速度的にはどうなのかな? というのが気になりますよね。
kotlinx.serialization のレポジトリ、ちゃんとベンチマークコードが入っているのに、結果をのせてないところが奥ゆかしいですね。
https://github.com/Kotlin/kotlinx.serialization/blob/4bf4113c2c6936e841caf2a8670382e701e7fcc2/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt
./gradlew jmh
とかすれば、結果が得られるので、手元で動かして結果を見てみると良さそう。
Benchmark Mode Cnt Score Error Units
JacksonComparisonBenchmark.jacksonFromString thrpt 10 731.704 ± 17.971 ops/ms
JacksonComparisonBenchmark.jacksonSmallToString thrpt 10 9779.674 ± 3045.866 ops/ms
JacksonComparisonBenchmark.jacksonToString thrpt 10 1811.471 ± 55.360 ops/ms
JacksonComparisonBenchmark.jacksonToStringWithEscapes thrpt 10 1508.877 ± 122.538 ops/ms
JacksonComparisonBenchmark.kotlinFromStream thrpt 10 1080.475 ± 82.937 ops/ms
JacksonComparisonBenchmark.kotlinFromString thrpt 10 1375.436 ± 89.197 ops/ms
JacksonComparisonBenchmark.kotlinSmallToOkio thrpt 10 8141.993 ± 111.842 ops/ms
JacksonComparisonBenchmark.kotlinSmallToStream thrpt 10 10325.260 ± 2784.316 ops/ms
JacksonComparisonBenchmark.kotlinSmallToString thrpt 10 10832.368 ± 306.662 ops/ms
JacksonComparisonBenchmark.kotlinToOkio thrpt 10 898.711 ± 26.997 ops/ms
JacksonComparisonBenchmark.kotlinToStream thrpt 10 1011.233 ± 62.126 ops/ms
JacksonComparisonBenchmark.kotlinToString thrpt 10 1912.123 ± 192.133 ops/ms
JacksonComparisonBenchmark.kotlinToStringWithEscapes thrpt 10 1336.079 ± 303.871 ops/ms
結果はこんな感じになる。
chatgpt で markdown に整形した結果が以下。そんなにすごい差があるわけでもない。
https://tokuhirom.github.io/kotlin-js-samples/
Kotlin/JS 面白いなぁとおもって試してみた。
ので、サンプルコード集を作ってみました。git clone するとすぐに試せます。
なかなかの力作なのでお試しあれ。
Kotlin/JS と CSS framework とか MUI のような UI library などについて考える。
kotlin/js を使うときのフレームワークは基本、どうするんだ。
compose-html のとき
MUI などは使えないので、bootstrap か tailwind みたいな CSS framework を使うのがよさそう。
tailwind は postcss とかの設定がだるめっぽい
react のとき
MUI 使った方が楽っぽい
kotlin-mui は GridProps.xs とかのプロパティが使えなかったりして悲しい気持ちになることがある。
kotlinx.rpc が楽しそうなので触ってみたのでメモ。
https://github.com/Kotlin/kotlinx-rpc/tree/main/samples/ktor-web-app
ここにあるサンプルがわかりやすくてすぐ動くので、これを触って
transport レイヤーは取り替え可能。
現在実装されてるのは ktor ベースで websocket + JSON
JSONRPC のような雰囲気の通信プロトコル。
Flow ベースで、ストリーミング処理も可能。
今後、grpc ベースのものが提供予定。サーバー間通信では gRPC のほうが使いやすそう。
管理画面とかだとこんなプロジェクト構造が良さそう。
common
プロジェクトに interface を定義
server
プロジェクトにサーバー側コードを定義。
frontend
プロジェクトに kotlin/js でコードを生成
path とかゴチャゴチャ悩まずに kotlin の interface だけ定義すれば良いのは楽かも。
とはいえ、websocket ベースだとログとか悩まないといけないという面もあり、難しいところ。
gRPC サポートはいって安定したら、サーバー間通信に使うと良いかも。
https://github.com/kotlin-hands-on/web-app-react-kotlin-js-gradle/issues/42
kotlin hands on の web-app-react-kotlin-js-gradle のコードを実行してみていて、、kotlin-wrappers の最新版に差し替えて動かしてみたら全く動かなかったんで、これ動くようにしてくれないかねえ、と言ってみた。今日現在は web-app-react-kotlin-js-gradle は kotlin-wrappers の 1.0.0-pre-430 を使っている。
kotlin-wrappers の pre-750 版だと document.getElementById("root")
の結果が org.w3c.dom.Element
なのに対して、createRoot
の引数は web.dom.Element
となっていて、、この2つは相互に変換することは出来なさそう。
どうしたらいいの?
ということで、issues で聞いてみた。
import web.dom.document
instead of import kotlinx.browser.document
で解決。
最近は keyball44 を利用しているわけだが、親指周りにキーがもっと多かったら便利なのかなぁと思い、YMGWorks さんの The endpoint を booth で購入し、試してみた。
トラックボールの位置は左手親指。トラックボールの位置の自由度が高いのは良いなぁ。一方で、ビルドガイドで「こうだったらこう。そうじゃなかったらこう」みたいな記述が多いので、注意して読まないといけないので集中力を要する。
左手親指においたらトラックボールの位置が結構遠くて、僕の手だとちょっとキツかった。
また、キーが多いとはいえ、多かったら多かったで、使うキーの種類をもっと増やしたいとはならないなぁ、と感じたので、keyball44 でちょうど良いのかも。と思った。
また、The endpoint のトラックボールモジュールは蓋がついていないため、傾けてるとすぐに外れて転がっていってしまうのがちょっと不便。トラックボールモジュールの追加部品をけぺおさんが作っているので、そういったものを使わないと、なかなか使いにくいかもしれず。
トラックボールモジュールに含まれているネジの種類が多くて、ネジの判別をするのが結構難しいので、マステの上に置いて数えながらやると良さそう。
ロータリーエンコーダーっていうのを初めて取り付けてみたが、自分的には用途がちょっと思い浮かばなかった。
などなど。
https://github.com/tokuhirom/keyball/commit/8ae41b881609df2692aa4daa899131a171976202
こんなパッチをファームウェアに当てた。
完全に自分用なので雑。
Special Key Code の 16 を押下している間だけ、スクロールモードに入ると同時に HORIZONTAL スクロールロックをかけるという感じ。
これにより、めっちゃ快適に横スクロールできるようになって最高。
まじおすすめです。
Discussions にも投稿してみた。
https://github.com/Yowkees/keyball/discussions/561
HTML の pre 要素を利用して表示させているときに、通常は長い行があっても折り返されない。
折り返したい場合は以下のようにする。
.wrap {
white-space: pre-wrap;
word-wrap: break-word;
}
./gradlew detekt
では一件も検出されないが、detektMain だと検出されまくるという時がある。
一般的な checkstyle などのプラグインではcheckstyleMain は checkstyle をメインソースセットに対してだけ実行するという意味なので、checkstyleMain と checkstyleTest を足したものにだいたい等しいのだが、detekt の場合は違う。
detektMain/detektTest の時は Type Resolution が有効になる という違いがある。
detekt の組み込みのルールの中にも、Type Resolution が有効じゃないと動かないテストも多いので注意が必要(NullableToStringCall など)。
./gradlew detekt
で CI していると./gradlew detektMain
では通らないコードができあがる。
rustmigemo という、migemo を rust で実現できる便利な crate がある。
これを利用しようとしたところ、入力文字列として "h" とか "s" を入れたときに、panic することがわかった。
これは、h とか s とかで始まる文字列として、surrogate pair が含まれているからだということが、panic したところに printf debug していたらわかった。
surrogate pair で検索するような文字を、migemo で探したいことはほぼないと思うので、実際にどのような文字が含まれているかを調べてみることにした。
python で以下のようなスクリプトをさらっと書いて、辞書ファイルをスキャンしてみる。
import codecs
import sys
def is_surrogate_pair(char):
"""
Checks if a character is in the surrogate-pair range.
"""
return 0x010000 <= ord(char) <= 0x10FFFF
infile = sys.argv[1]
with codecs.open(infile, 'r') as file:
for line in file:
has_surrogate_pair = False
for char in line:
if is_surrogate_pair(char):
print("U+%X" % ord(char))
line = line.replace(char, f'**{char}**')
has_surrogate_pair = True
if has_surrogate_pair:
print(line)
実行したところ、以下のような文字だということがわかった。
U+20B9F
しか 仕懸 仕掛 仕替 併 兪 叱 史家 呵 四角 始華 子夏 子華 市価 市花 師家 志嘉 志賀 歯科 死花 然
知客 確 確然 私家 糸価 紙価 紙花 緊 而 聢 芝河 詆 詞花 詞華 詩家 詩歌 賜暇 蹙 顰 飾
**𠮟**
U+20B9F
しっ 七 卓 叱 執 失 嫉 尻 悉 櫛 湿 漆 濕 疾 確 緊 質 **𠮟**
U+20B9F
しつ 七 仕付 仕詰 叱 喞 執 失 嫉 室 志都 悉 桎 湿 漆 濕 為付 為詰 瑟 疾 膝
蟋 貭 質 躾 隰 隲 **𠮟**
U+27631
ふき 不帰 不羈 不覊 不記 不諱 不軌 付記 吹 富喜 富貴 布岐 腐気 苳 葺 蕗 袘 附記 **𧘱**
人生で一度も使おうと思ったことがない漢字なので、これはまぁ検索出来なくてもいいだろうという気分になった。
サロゲートペアを含む文字を除外する処理を辞書作成スクリプトから除外すればいいな、と思ったが、、 yet-another-migemo-dict ではビルドツールが全部含まれてるわけじゃなくて手である程度ビルドする感じになっていたので、めんどくさいからスクリプトを書いて、再生成可能なようにした。
https://github.com/oguna/yet-another-migemo-dict/pull/3
https://discuss.codemirror.net/t/codemirror-lang-markdown-with-codemirror-legacy-modes/7925
Codemirror6 では、一部のメジャーなプログラミング言語はサポートされているが、5時代にはサポートされていたのに6ではサポートされていないプログラミング言語も多い。
そういったプログラミング言語は @codemirror/lang-legacy-modes を使うことで、利用可能だ。
@codemirror/lang-legacy-modes のドキュメントを参照すれば、Codemirror6 でどのように使うかは書いてある。
しかしながら、Codemirror6 の lang-markdown に対してどのように使うかは書いてなかった。
そこで、フォーラムで質問したところ、@marjin 氏から即座にレスがついたので大変ありがたかった。
@codemirror/language-data
というパッケージを使うと、必要なプログラミング言語の文法を動的に読んでくれるらしく、lang-legacy-modes の中身もすぐに使えるようだ。
最高!
tauri 2.0.0 beta1 が出た。今後は大きい breaking changes はなさそうってことなので、移行してみる。
趣味で作ってるメモアプリを tauri 2.0.0 beta1 に移行した。
趣味だから、まぁ beta でもいいかな的なやつです。
npm install @tauri-apps/cli@next
npm run tauri migrate
ってやると、一通り自動マイグレーションされるので、それでだいたいいける。
が、微妙にぶっ壊れるので、新規で skelton 作って、それとの差分を見ながら調整していくみたいな作業はやっぱり必要。
fs まわり
[dependencies]
tauri-plugin-fs = "2.0.0-beta.0"
というふうに rust 側の Cargo.toml に依存追加する。
src-tauri/src/main.rs
に以下のように書いて、プラグインを有効化する必要がある。
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
}
JS の API で、renameFile が rename になったり、removeFile が remove になったりと、メソッド名もそこそこ変わっている。
各APIの dir 指定が baseDir
という名前に変わってる。rename の基底ディレクトリが newPathBaseDir と oldPathBaseDir に別れたの最高っぽい。
あとは、 credentails 周りが変わってる。 migration ツールが src-tauri/capabilities/migratred.json
に権限リストを移行させてくれるが、権限の名前が変わってるので、そのままだと起動しなくなる。
例えば以下のようなエラーになる。
Permission fs:allow-read-file not found, expected one of app:default, app:allow-app-hide, ...
appdata なら appdata に読み書きする権限が、個別で用意されるようになってるんで、ここのへんのフォルダだけ触る場合は以下のように書いたらよろしい。
{
"fs:allow-appdata-write-recursive",
"fs:allow-appdata-read-recursive"
}
メニューが便利
Menu が rust ではなく JS 側でできるっぽいがドキュメントがまだなさそう。
情報が全く見当たらない。これはしばらく待ってから対応したいところ。
SQL が使える
sqlite が組み込みになっている。便利。
まとめ
わりと簡単に移行できる。