この記事は Kotlin愛好会 vol.27 向けに作成したものになります。
JGit の Kotlin ラッパーライブラリである KGit を作るまでの日記になります。
JGit との出会い
Git を使わずに Git を使おう
ライブラリを作るきっかけになった話です。それまでGoogleDriveを使っていたテクスチャ管理をGitHubに移行することになりました。しかし、テクスチャを描いてくれている人はGitを使ったことがなく、そんな人でもGitHubを上手いこと利用できないかと考えました。勿論、Gitを教えればよかったのですが、あまり言わないでください。
そんな時に見つけたものが JGit になります。これは Java で Git の機能を実装しているライブラリになります。JGit 自体は Eclipse が開発しているライブラリで、実際に EclipseIDE の中でも EGit という形で登場します。EclipseIDE で使われているのもあり、今も開発され続けています。
GitHub 連携のアプリを作ろう
JGit を使う処理は以下のようになります。
- リポジトリのクローン・アップデート
- コミット・プッシュ
また、管理の上で以下のような機能を載せています。
- 軽量化Zipの生成
- Jsonファイルの構文確認(未実装)
ユーザー側からすると、ファイルを追加・編集してからファイルを実行してコミットメッセージで書くだけで残りは自動で実行してくれます。この実行ファイルをテクスチャと一緒にリポジトリ内に入れておくことで連携アプリのバージョンも管理しています。
自己アップデートは出来ない
アプリ自体にアップデート機能がついており、アプリを開いた際に「更新があります。更新しますか?」のようなウィンドウが出てくるというものは一度は目にしたことがあると思います。しかし、あの機能には単純に実装できるものではありません。
何故なら、実行中のファイルを上書きすることは出来ないからです。別でアップデート用の実行ファイルを用意しておき、それを実行する形や、ダウンロードだけ済ましておいて次回の実行時にファイルを置き換える処理を行う形で対応できる問題になります。
さて、Git には pull という機能があります。JGit は .jar の中で動くのでもしも .jar 自体に変更があればどうなるでしょうか?結果は Windows で例外が投げられます。
私が Mac 環境でアプリの開発をしていますが、テクスチャを変更する人は Windows 環境です。「私は例外出なかったのになんで?」 と困惑しました。Mac, Linux(私の知っている範囲) では実行中のファイルに変更を加えても特に何も言われず変更されます。Windows では変更がキャンセルされます。そういった OS の問題なんじゃないかなと思います。
「JGit の pull で更新が出来ない。」 とても悲しい話です。結果、リモート側に変更があった時に、アップデート用のディレクトリにリポジトリをクローンして、次回実行時にファイルの置き換えを行う方法で対応させました。実際のところ、ダウンロードの量を減らす工夫は出来たと思いますが、変更を加える人は基本的に一人なので妥協しました。
JGit はつらいよ
Builder Class
Builderクラスを作り、メソッドチェーンによって処理の設定を行う。というものを Java のライブラリでよく目にします。JGit も実際そうなっています。メソッドチェーンが悪という訳ではないのですが、個人的に好きではありません。例えば StringBuilder
であれば、
val text = StringBuilder()
.appendLine("Line1")
.appendLine("Line2")
.toString()
と書くよりも、
val text = StringBuilder().apply {
appendLine("Line1")
appendLine("Line2")
}.toString()
と書く方が綺麗に見える気がします。
こう書くのであれば buildXXX
というメソッドを定義してそれを使うことで綺麗になります。
inline fun buildString(action: StringBuilder.() -> Unit): String {
return StringBuilder().apply(action).toString()
}
を定義することで
val text = buildString {
appendLine("Line1")
appendLine("Line2")
}
このように書くことができます。buildString
は stdlib 内で定義されているのでそれを使うことにしましょう。
メソッドチェーン廃止運動
JGit ではメソッドチェーンで Git のコマンドの設定を行います。例えばクローンであれば、
Git.cloneRepository().setURI("https://github.com/~~~~").call()
と書きます。これをメソッドチェーンではなく、ラムダで書きたいと思えば、
Git.cloneReposity {
setURI("https://github.com/~~~~")
}
のようになります。ですが、わざわざ自分で定義していくのは面倒です。
「そうだ、ライブラリを作ろう。」
JGit から KGit へ
Null 系アノテーションは必須
Kotlin で Java のライブラリを使う上で、大きな障害になるのは、Nullableかそうでないかです。Java のライブラリ側に @Nonnull
, @NotNull
, @Nullable
などがついているとそれを考慮してくれます。逆に言えば、ついていなければ自分で判断することになるので面倒です。JGit ではついているものもあれば、ほとんどがついていません。Java で作ったライブラリを Kotlin で使うのであれば、Java 側にアノテーションをつけておくことは大切だと思います。
委譲プロパティが異常
ゲッターやセッターが Java で定義している場合、プロパティとして扱えます。
// Java
public CommitCommand setMessage(String message) {}
public String getMessage() {}
というようなメソッドに対しては
asJ.message = "~~~"
println(asJ.message)
と書くことができます。
プロパティをラッパする時、 by
を使って委譲プロパティを使うことができます。上の例では、
var message: String? by asJ::message
と書くことができます。
別の例として、
// Java
public AddCommand setUpdate(boolean update) {}
public boolean isUpdate() {}
というように、get
ではなく、 is
というメソッドの場合、
var isUpdate: Boolean by asJ::isUpdate
と書くことはできません。ですが、
var isUpdate: Boolean
set(value) {
asJ.isUpdate = value
}
get() = asJ.isUpdate
というように書くことはできます。なぜ、ゲッターやセッターはプロパティとして書けるのに、委譲プロパティにできないのか悩みましたが、諦めて上のように書きました。
ゲッターはありません!
// Java
public CommitCommand setMessage(String message) {}
public String getMessage() {}
というようなメソッドを
asJ.message = "~~~"
println(asJ.message)
と書くことが出来るという話をしましたが、
// Java
public CommitCommand setMessage(String message) {}
となっていてゲッターが無かった場合は、Kotlinでどのようになるでしょうか。
asJ.message = "~~~"
と書くことはできず、
asJ.setMessage("~~~")
としか書くことが出来なくなります。ゲッターが無かったり有ったりすると、メソッドになったりプロパティになったりと分かりにくくなります。Java のライブラリでゲッターを定義しておけば、プロパティとして値の変更が出来るので綺麗に見えるかもしれません。諸説あります。
省略したりしなかったり
// FetchCommand, PullCommand
fun setTagOpt(tagOpt: TagOpt?) {
asJ.setTagOpt(tagOpt)
}
// CloneCommand
fun setTagOption(tagOption: TagOpt?) {
asJ.setTagOption(tagOption)
}
FetchCommand
や PullCommand
では、setTagOpt
というようになっているのに対して、CloneCommand
では setTagOption
となっています。なのに、クラス名は TagOpt
になっています。省略系について統一しておかないと気持ち悪いことになってしまいそうです。
ライブラリを公開しよう
GitHub Packages
「GitHub Actions でデプロイするなら、GitHub Packages でいいか!!!!」 という軽いノリから採用しました。しかし、GitHub Packages で公開されている Maven Repository を使う場合、新しくURLを追加しないといけないことに加え、GitHub の認証情報を書かなくては使用することができません。
repositories {
maven {
url = 'https://maven.pkg.github.com/~~~/~~~'
credentials {
username = project.hasProperty("GITHUB_USER") ? GITHUB_USER : ''
password = project.hasProperty("GITHUB_TOKEN") ? GITHUB_TOKEN : ''
}
}
}
Bintray / JCenter
簡単に使える有名なものという安直な考えから採用しました。雑にやっても公開できたという感想です。JCenter に公開する場合、申請を書くのですが、適当に英文をちょっと書く程度でいいので、思っていた以上に簡単に進みました。
しかし、Bintray や JCenter のサービス終了の知らせを受けて移行を余儀無くされました。
Maven Central
ライブラリを載せる時点で申請が必要ということで使おうとは考えていませんでしたが、ちょうどいい機会なのでやってみることにしました。手順に従って申請を出せばその日のうちにライブラリを公開できるところまで終わるくらいには簡単でした。バージョンを公開する時も必要なファイルが揃っているか、内容が間違っていないかの確認を行ってくれるので、間違えることなく公開できるのは面倒なことではなく良いところだと思います。
まとめ
JGit のラッパーライブラリとして KGit を作る中で、様々な経験が出来たのでよかったな~と思っています。MavenCentral を使ってライブラリを公開することに抵抗を感じなくなったので、積極的にライブラリを作っていきたいと思います。
スターくれたらうれしいな…