Goを書けなくたってビルドはできる！初心者目線でのHomebrewのFormula作成体験記


# ミリしらでもやればできます
Macを使っているエンジニアであればだいたいの人間がお世話になっているであろうのがHomebrewというパッケージマネージャー。わざわざコンパイル環境を整えてビルドするっていう作業をしなくても、簡単にコマンドラインツールが導入できたりして便利ですよね。

もうすでにだいたいのツールがHomebrewで導入できる便利な時代なのですが、それでもやはりないものはなかったりします。すでに開発者の方でバイナリを用意して配信してくれていたりするのですが、「違う、そうじゃない。俺はHomebrewで管理したいんだ！」という謎の病気を患ってしまった私は、せっかくだからと本家リポジトリからお目当てのツールが使えるよう、Formulaの作成に挑戦してみました。

その際にHomebrewのDSLはおろか、Goのコードを1文字すら書いたことのない私は、プルリクの段階で大変まごついてしまったため（メンテナーの皆さんごめんなさい）、今後Formula作成に挑戦しようと思う方が同じ轍を踏まぬよう、困ったところの解説みたいなものをざっくり書いてみようと思います。Goで作成されたツールのFormulaを作りたいと思っている方は、是非参考にしてもらえれば幸いです。

今回マージしてもらったFormulaが[こちら](https://github.com/neronplex/homebrew/commit/01032855bde6fc511aad4011f47db0c2bfd8bba2)です。こちらを例として説明します。公式リポジトリの[Formula Cookbook](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md)も併せてご覧ください。

## 以下解説です

### 冒頭
Goで作成されたプロジェクトのFormulaを作成する場合は、冒頭の`require "language/go"`が必須です。

### 4行目から7行目
このあたりは特に考えることはありません。素直に設定していきましょう。ダウンロードしてくるソースコードのバージョンを指定することもできますが、GitHubなどでホストされているソースコードでは、URLからバージョンを検知することができるようになっているため、その場合はバージョンの指定は不要です。

そのような場合にバージョンを指定する定義がFormulaにあった場合、スタイルガイドラインに引っかかるためJenkinsのビルドテストを通過することができません。

### 9行目から10行目
`depends_on`ではこのFormulaをインストールするために必要なFormulaを指定します。このFormulaではGoとDockerが必要なため、それを指定してあります。`:build`はFormulaをインストールするためだけに必要であることを意味します（ビルド時のみインストールします）。`:recommended`は必須ではないけどもインストールすることが推奨であることを意味しています。

### 12行目から18行目
`go_resource`はGoのソースコードをビルドするために必要なパッケージを指定します。普段のGoのビルドでは`go get <url>`を使ってパッケージを引っ張ってきますが、HomebrewのFormulaではこちらを使用することが推奨されています。

installメソッドの前に`go_resource`で必要なパッケージを定義しておくことで、installメソッド内でメソッド（後述）を1発叩くことで、定義してあるものをまとめて持ってきてくれます。

```go_resourceの例
# go_resourceとdoの間で指定した名前でフォルダが作成され、そこにライブラリがインポートされます
# インポート先は後ほどコールするメソッドの方で指定するので、ここではあまり気にしなくていいです
go_resource "github.com/tools/godep" do
	# URLは配信されているGitなどのリポジトリ
	# リビジョンは安定版としてリリースされているリビジョンを指定します
	url "https://github.com/tools/godep.git", :revision => "e2d1eb1649515318386cc637d8996ab37d6baa5e"
end
```

今回は、開発にパッケージ管理ツールであるGodepを使用しているため、定義してあるものは2つだけです。開発者の方がパッケージ管理ツールを使用していない場合は、必要なものを全て書き出す必要があります。配布しているソースコード内に`Godeps`というフォルダがある場合は、依存解決をGodepに任せることができます。

### def install

#### 21行目から22行目
ソースコードのmainパッケージを見て、自分自身をインポートしているようであれば、自分自身に対してシンボリックリンクを張る必要があります。

Goの依存解決は、srcフォルダに入っているものを起点として行われるため、書いてあるような感じで定義を書いてやると、シンボリックリンクを張ってくれます。

#### 24行目
Goの依存解決は、環境変数である`$GOROOT`ないしは`$GOPATH`直下の`src`フォルダを見て行われるため、`$GOPATH`をHomebrewでビルドする際のフォルダに合わせてやる必要があります。ただ、Homebrewでは環境依存を極力減らすため、ビルド時は`/private/tmp`直下にビルドごとにフォルダを作ってからビルドを開始するため、ビルドフォルダが一定しません。

そこで、`buildpath`という変数にビルド時のフォルダパスがそこに入っている仕組みになっているため、`$GOPATH`に`buildpath`を設定（代入）してやります。

`buildpath`などのパス（ロケーション）に関する変数は、[こちら](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md#variables-for-directory-locations)に使えるものが一覧となっています。

#### 25行目
このメソッドを実行すると、先に`go_resource`で定義しておいたパッケージが、一気に指定したフォルダへとインポートされます。インポート先は、特殊な事情がない限りは`buildpath/"src"`でいいと思います。

#### 27行目から29行目
ここでは先ほどインポートしてきたGodepのインストールを行っています。Godepがインポートされているフォルダに移動し、`go install`を叩くという動作になります。1つのクオーテーションでコマンドをまとめて書くのではなく`system "command", "arg1", "arg2"`といったように、引数などはカンマを使って記述することが推奨されています。

`go install`を実行すると、`buildpath/bin`直下にgodepのバイナリが作成され、godepを用いた依存解決を行うことができるようになります。

#### 31行目から33行目
ここでいよいよソースコードからビルドを行う記述になっています。今回はGodepを使って依存解決したビルドを行いますが、普段と違って何も難しいことは無く、`go build ...`の前に`godep`を付けるだけとなっています。

`go build -o`で出力先を指定することができるため、今回のように`bin/"fugu"`としてやるだけで、決まったCellarのディレクトリ（今回で言えば`/usr/local/Cellar/fugu/1.1.1/bin`）に"fugu"という名前でバイナリを作成してくれます。

また、これもGoの仕様を知らなくて詰まっていた部分なのですが、Goのmainパッケージが複数コードに分割されている場合、今回の例のようにビルドコマンドで全て指定しないとコンパイルに失敗します。素人丸出しです。

あとは、binに生成されたバイナリに`/usr/local/bin`からシンボリックリンクを張ってくれるため、これで基本的にはビルド完了です。

### 36行目から38行目
HomebrewではFormula単位でテストを書くことが推奨されているため、簡単にでもいいのでテストを書きましょう。テストがないと、スタイルガイドラインに違反しているため、Jenkinsのビルドテストを通過できません。

今回の例では、`fugu --version`を実行し、出力結果が`1.1.1\n`でなかった場合はエラーが起きます。

## 最後に
まあやはり慣れないうちはFormulaを書くのは難しいと感じるかと思います。実際自分も慣れなくて、空いた時間で書いてはテストを繰り返していたら、最終的にマージされるまで数週間かかってしまいました。

しかし、みんなでFormulaを書いて使えるツールを増やせばよりHomebrewは便利になっていきます。誰かが1回書いて公開してくれれば、別の人が同じことをまたやる手間も省けますので、皆さんも積極的にFormulaを書いて公開してみてはいかがでしょうか。

~~ちなみに、自分が今回導入したツール(fugu)は、使い方が未だに分からずまだ1回もちゃんと使えていません。意味ないですね。~~

## 追加情報
最近Formulaを書いていて気がついたことなのですが、どうやらGitHubで開発が進められているプロジェクトは、フォーク数やスター数など"notable"かどうかでFormulaをAcceptするかという基準が設けられているようで、一定数以下（10フォーク or ウォッチ以上か20スター以上）だとビルドテストの段階でこけるようです。GitHubで進められているプロジェクト以外でも、Google検索でヒット数が少ない場合などに、メンテナーから事情を聞かれるようです。

この場合はプルリクを出してもAcceptされる可能性が低いため、[自分でリポジトリを立て](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/How-to-Create-and-Maintain-a-Tap.md)そこで公開した方がいいでしょう。

# 参考文献
* [homebrew/Library/Formula/the_platinum_searcher.rb
](https://github.com/Homebrew/homebrew/blob/master/Library/Formula/the_platinum_searcher.rb) - メンテナーの方から、Goのビルドはこれを参考にしてね！と勧められたものです。
* [Homebrewに新しいFormulaの追加リクエストをしてみた](http://dev.classmethod.jp/tool/new-formula-request-to-homebrew/)
* [HomebrewのFormulaの作り方](http://selfkleptomaniac.org/archives/1418)
* [HomeBrewで自作ツールを配布する](http://deeeet.com/writing/2014/05/20/brew-tap/)