Open6

jarで作成したCLIツールをhomebrewで配布するまで

Kesin11Kesin11

Kotlinの勉強ついでにCLIのツールを作成したのですが、いざこれを実際にmacやLinuxで実行するために開発環境とは別のマシンに配布しようと思ったときに悩みました。
macで同じみのhomebrewを使うと自作のツールでも簡単に配布できることが分かったので、その方法のメモ

Kesin11Kesin11

jarのツールもjava -jarなしで実行したい

今ではKotlin Multiplatformというものがあるので必ずしもそうではないが、Kotlinで作成したプログラムを実行可能な形にしようとするとjarになる。
gradleなど無しで単独で実行するには java -jar hoge.jar のようにどうしても java -jar が必要になる。これはさすがにかっこ悪いのでなんとかしたい。

世の中のツールで java -jar を付けさせるものは見たことがないので何とかする方法があるはず。というわけでまずはjava製の代表的なツールであるJenkinsがどうしているのか調べてみた。

ちょうど手元のmacにはhomebrewでJenkinsをインストールしていたのでこれを調べてみる。

$ cat `which jenkins`
#!/bin/bash
JAVA_HOME="$(/usr/libexec/java_home --version 1.8)" exec java  -jar /usr/local/Cellar/jenkins/2.234/libexec/jenkins.war "$@"

なるほど、bashのラッパースクリプトを用意しているのか。頭いい。
$@ はスクリプトの引数すべてを表しているので jenkins --httpPort=8080 のように引数を渡した場合、java -jar jenkins.war --httpPort=8080 のように実行したのと同じことになるというカラクリになっている。

特にデメリットもなさそうなので、jarとセットでこのようなラッパースクリプトを用意してやれば見た目上はgoなどで作られたシングルバイナリのツールと遜色ない形にできそうなことが分かった。

Kesin11Kesin11

jarのツールをhomebrewで配布するFormulaを見てみる

このjenkinsのコマンドがhomebrewではどのように配布されているのかを見てみる。
https://github.com/Homebrew/homebrew-core/blob/3b4d7a9387fadc6a0a4f7906e1600756fbb2bb91/Formula/jenkins.rb

まだ分からないことだらけだが、
url "http://mirrors.jenkins.io/war/2.279/jenkins.war" がおそらくダウンロードするwarの場所、
depends_on "openjdk@11" が依存パッケージ、
def install がインストール処理だと想像できる。
ここでinstallの中の bin.write_jar_script libexec/"jenkins.war", "jenkins", java_version: "11" が気になった。write_jar_script とは?これをgithubの検索で調べてみる

https://github.com/Homebrew/homebrew-core/search?q=write_jar_script&type=code
javaやKotlinで有名なツールがザクザク出てきた

そして関連してそうなissueも発見。

差分を見てみると、どうやら昔は多くのパッケージでbashのラッパーを手書きしていたようだがそれらをまとめて write_jar_script に書き直している。
ということは、Jenkinsにもあったbashのラッパーはhomebrewが提供している機能らしい。

Jenkins以外のツールも発見できたことで、Formulaファイルにどういう内容を書けばいいのか大体想像がついたので次のステップに進む

Kesin11Kesin11

オレオレtapを作る

いきなりhomebrew本家にPRを出すとかえって面倒そうなので、まずはオレオレtapを作成ししてしばらくはそこで運用したい。

https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap

  • github上で homebrew-xxx というリポジトリを用意すると brew tap で取り込むことができる
  • brew tap-new でtapのテンプレートが作成できる

ななめ読みしたところ、上記のことが分かったのでまずはテンプレートを作成する。

(以下の実行環境はWindowsのWSLで動かしているubuntu)

$ brew tap-new Kesin11/homebrew-tap
Initialized empty Git repository in /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/kesin11/homebrew-tap/.git/
[master (root-commit) 4e04054] Create kesin11/tap tap
 3 files changed, 85 insertions(+)
 create mode 100644 .github/workflows/publish.yml
 create mode 100644 .github/workflows/tests.yml
 create mode 100644 README.md
==> Created kesin11/tap
/home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/kesin11/homebrew-tap

When a pull request making changes to a formula (or formulae) becomes green
(all checks passed), then you can publish the built bottles.
To do so, label your PR as `pr-pull` and the workflow will be triggered.

tapのディレクトリが作られるので、最初にgithubにもpushしてしまう。LICENSEが含まれていなかったので、リポジトリ作成時にgithubに作ってもらったファイルをコピペしてinit commitに含めた。
https://github.com/Kesin11/homebrew-tap/commit/fa862219156131b22e27a2428fd1651773e4071f

Kesin11Kesin11

Github Releasesを作成する(jarをアップロードする)

後述する brew create を実行するには、まずリリース予定のjarをパブリックなところにアップロードする必要がある。場所はどこでもいいと思うが、個人の制作物なので一番手軽なGithub Releaseを使うことにする。

暫定的なtagを打ち、Github Actionsでアーティファクトに保存していた最新のjarをアップロードした。
https://github.com/Kesin11/SkyWarehouse/releases/tag/v0.1.0-beta.1

Formulaをテンプレートから作成する

https://docs.brew.sh/Formula-Cookbook

ドキュメントを見ると brew create <URL> でFormulaのテンプレが作成できると書いてあるので、 アップロードしたjarのurlをコピーして以下のようなコマンドを実行する。
オプションは brew create --help を見て良さげなものを追加した。

brew create --set-name skw --set-license MIT --tap Kesin11/homebrew-tap https://github.com/Kesin11/SkyWarehouse/releases/download/v0.1.0-beta.1/skw.jar

すると先ほど作成したtapの中のFormulaに新しいファイルが作成されている。特に一番めんどくさそうな sha256 を自動的に埋めてくれるのが嬉しい。
事前に調べていた他のjar製ツールのFormulaを参考に必要な項目を埋めていく。

class Skw < Formula
  desc "Tool making cloud object storage as a artifact repository"
  homepage "https://github.com/Kesin11/SkyWarehouse"
  url "https://github.com/Kesin11/SkyWarehouse/releases/download/v0.1.0-beta.1/skw.jar"
  sha256 "2109b9e1e75c443bfc8095ea027e130e6a343b76ce50ac56d59e36cc42ddf5de"
  license "MIT"

  bottle :unneeded

  depends_on "openjdk@8" => :recommended

  def install
    libexec.install "skw.jar"
    bin.write_jar_script libexec/"skw.jar", "skw"
  end

  test do
    assert_match "Usage: Sky Warehouse options_list", shell_output("#{bin}/skw --help")
  end
end

ちなみに test として実行する内容を単に --help にするのはアンチパターンとドキュメントには書かれているが、今回のツールはいずれのコマンドも実行するためにGCPの認証情報が必要なため、やむを得ず --help が実行できるかどうかだけをチェックしている。
別にちゃんと実装するのが面倒だったからではない

後はいつもhomebrewを使うのと同様に brew install skw と実行するとGithub Releasesからjarをダウンロードし、bashのラッパースクリプトも含めてインストールしてくれてPATHも自動的に通してくれる。

以上、これだけで自作のツールをhomebrewで配布することができる。便利~~

アップデートの方法

TODO

少なくとも urlsha256 は更新する必要があるはずだが、手動でやると面倒くさい。自動でやるツールが何かしらあると思われるので調べたら追記するかも。

Kesin11Kesin11

参考

tapについて
https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap

Formulaの基本について。まずはここを見るとよさそう。
https://docs.brew.sh/Formula-Cookbook

homebrew本家に取り込んでもらうために守ってほしいことが書いてあるっぽい。オレオレtapで使う分には遵守しなくても問題ないだろうが、一読しておいた方がよいだろう
https://docs.brew.sh/Acceptable-Formulae

FormulaのDSLのリファレンス(DSL=rubyなのでrubyのドキュメント)
参考にするFormulaを読んでいて分からない記述が出てきたらここで調べる
https://rubydoc.brew.sh/Formula