Docker TUI ツール docui に Homebrew 対応のプルリクエストを送った話

## はじめに

先日、@gorilla0513 さん作の Docker TUI ツールである [docui](https://github.com/skanehira/docui) に対し、Homebrew 対応のプルリクエストを作成し、マージいただきました！

[Add goreleaser to support Homebrew installation by gotchane · Pull Request #102 · skanehira/docui · GitHub](https://github.com/skanehira/docui/pull/102)

おそらく次回のリリース分から使えるようになるのでは、と思います。レビューいただきありがとうございました！

そこで、今回の対応のいきさつ・ふりかえって学んだことをまとめます。

## tl;dr

今回の経験から、Issue に手を挙げ、対応にチャレンジすると、以下が得られることがわかりました。

* できるかぎり良い対応をしようと考え・議論する機会が得られる
* Issue にチャレンジした結果、新たな知見と大きな達成感が得られる

## いきさつ

### docui に出会う

docui との出会いは、以下の記事を読んだことでした。

[Docker初心者にオススメのゴリラ製ツール - Qiita](https://qiita.com/gorilla0513/items/1211898cf3e09ba6f6f0)

実際に使ってみると、Docker の情報をターミナル上で視覚的にわかりやすく表示してくれて、とても素敵なツールだと思いました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/48242dd2-722e-b522-bef8-8945dbcb356a.png)

そこから興味が湧き、継続的にプロジェクトをウォッチしていました。

### Homebrew Support の Issue に手を挙げる

そんなある日、docui のプロジェクトに以下のような Issue が作成されていました。Homebrew からのインストールをサポートしてほしい、という内容でした。

[Brew support · Issue #91 · skanehira/docui · GitHub](https://github.com/skanehira/docui/issues/91)

僕自身 Homebrew の定義ファイルを作成したことはなかったのですが、ちょうど僕も「Homebrew のインストールに対応したら便利だろうな」と考えており、かつ Homebrew でのインストールの仕組みを勉強したかったので、手を挙げてチャレンジしてみることにしました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/7834c30e-0341-3c9e-9dcb-6e2077e7f5cf.png)

### Homebrew の調査・Issue 上で相談

まずは Homebrew の使い方の調査から始めました。すると Homebrew の対応には、大きく分けて2種類あることがわかりました。またそれぞれのメリット・デメリットも整理してみました。

方法|メリット|デメリット
:--|:--|:--
[公式リポジトリにプルリクエストを送る方法](https://docs.brew.sh/How-To-Open-a-Homebrew-Pull-Request)|1コマンドでインストール可能。Homebrew を使っていたらデフォルトでインストールできるようになる|バージョン更新の度 PR 作る必要があり、最新バージョンの追従が大変
[Homebrew Tap を使う方法](https://docs.brew.sh/Taps)|自前でリポジトリを準備できるため最新バージョンの追従がしやすい|docui 用のリポジトリを `brew tap` でインストールする手間が増える

しかし調べたはいいものの、どちらの方法を取るべきなのかは決めきれませんでした。この状態でいきなりプルリクエストを作成しても的を外してしまう可能性があったので、この時点で Issue 上で質問してみました。

内容は、公式の Homebrew への反映が優先なのか、Homebrew Tap ですぐ使えるようにするのが優先なのか、という質問をしました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/01502488-6558-6ef6-d217-12ec3cb26f44.png)

すると、@gorilla0513 さんから返答が。

どちらの方法も GitHub Releases を使う方法だと思うが、可能であれば最新コミットの master からビルドしたい、また goreleaser から Homebrew を自動生成できると便利かもしれない、というアドバイスをいただきました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/d8c2377b-8b77-58dd-38cd-88a1bb080b93.png)

確かに公式リポジトリにしても Homebrew Tap にしても GitHub Releases のバイナリファイルを使うことになるので、まず Homebrew の自動生成の部分から試してみることにしました。

### goreleaser 検証

いただいたアドバイスをもとに、まず goreleaser をローカル環境でどう使うか検証しました。

[goreleaser](https://github.com/goreleaser/goreleaser) とは、リポジトリに指定された GitHub tag をもとに、リリース用のバイナリファイルを自動でビルドし、GitHub Releases にバイナリファイルを自動で登録してくれるツールです。以下のようなイメージです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/a6dff866-462b-4bd7-68ca-9aa1f5f4e91d.png)

docui を自分のリポジトリに fork し、goreleaser の公式ドキュメントにある [Quick Start](https://goreleaser.com/quick-start/) を試しました。

コマンドは以下のようなイメージです。実際のリリースはせず、dry-run でビルドを試しました。

```bash
$ brew install gorelaser
$ cd /path/to/docui
$ export GITHUB_TOKEN=`YOUR_TOKEN`
$ goreleaser init
$ goreleaser --snapshot --skip-publish --rm-dist
```

こちらを実行すると、`dist/` ディレクトリに以下のようなリリース用のバイナリファイルが作成されました。ワンコマンドでバイナリファイルを生成してくれるのは非常に便利でした。

```bash
docui_1.0.3_Darwin_i386.tar.gz
docui_1.0.3_Darwin_x86_64.tar.gz
docui_1.0.3_Linux_i386.tar.gz
docui_1.0.3_Linux_x86_64.tar.gz
```

### goreleaser から Homebrew Tap 設定ファイルの自動生成検証

バイナリファイルを生成できることが確認できたので、次は goreleaser で Homebrew Tapの自動生成が可能かどうかを検証しました。

詳しい設定方法は以下の記事が詳しいので、ここでは概略のみ記載します。

[goreleaserでHomebrewのFormulaを自動生成する - Qiita](https://qiita.com/knqyf263/items/53dd0d0916afc5472281)

まず自分の GitHub アカウントに、`homebrew-docui` という名前のリポジトリを作成し、`Formula` というフォルダを作成しておきます。

https://github.com/gotchane/homebrew-docui

次に docui の goreleaser の設定ファイルである `.goreleaser.yml` に、`brew` で始まる設定を追加します。

```yml:.goreleaser.yml
project_name: docui
builds:
- env:
  - CGO_ENABLED=0
  - GO111MODULE=on
archive:
  replacements:
    darwin: Darwin
    linux: Linux
    386: i386
    amd64: x86_64
checksum:
  name_template: 'checksums.txt'
snapshot:
  name_template: "{{ .Tag }}-next"
changelog:
  skip: true
brew:
  name: docui
  github:
    owner: gotchane
    name: homebrew-docui
  folder: Formula
  description: "TUI Client for Docker"
  homepage: "https://github.com/skanehira/docui"
  commit_author:
    name: goreleaserbot
    email: goreleaser@carlosbecker.com
  dependencies:
    - go
  install: |
    bin.install "docui"
  test: |
    system "#{bin}/docui"
```

設定できたら上記をもとにコマンドを実行します。

```
$ goreleaser release
```

実行すると、GitHub Releases にバイナリファイルが登録されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/a6dff866-462b-4bd7-68ca-9aa1f5f4e91d.png)

また `homebrew-docui` リポジトリに Homebrew Tap の設定ファイルも自動で生成されます。以下のようなファイルです。

https://github.com/gotchane/homebrew-docui/blob/master/Formula/docui.rb

```ruby
# This file was generated by GoReleaser. DO NOT EDIT.
class Docui < Formula
  desc "TUI Client for Docker"
  homepage "https://github.com/skanehira/docui"
  url "https://github.com/gotchane/docui/releases/download/1.0.3/docui_1.0.3_Darwin_x86_64.tar.gz"
  version "1.0.3"
  sha256 "d179ac5cd641d9545fda90226421a994db468414f5a8ec4f74ab21ef4f930d36"
  
  depends_on "go"

  def install
    bin.install "docui"
  end

  test do
    system "#{bin}/docui"
  end
end
```

goreleaser でリリースを実行する度、Homebrew の設定ファイルも更新されるという形で使えるのはかなり好感触でした。

### CircleCI による goreleaser 自動実行検証

ここまでで、ローカルでの goreleaser コマンド実行でのリリースが確認できたので、あとは CI による goreleaser の自動実行を試しました。

[Continuous Integration · GoReleaser](https://goreleaser.com/ci/) を参考に設定しました。

元ファイルは以下です。

https://github.com/skanehira/docui/blob/master/.circleci/config.yml

以下、関係のある設定のみ抜粋します。

```yml:.circleci/config.yml
version: 2.1

executors:
  build:
    parameters:
      go-version:
        type: string
    docker:
      - image: circleci/golang:<< parameters.go-version >>
        environment:
          GO111MODULE: "on"
    working_directory: /go/src/github.com/skanehira/docui

commands:
  go_mod_download:
    steps:
      - restore_cache:
          name: Restore go modules cache
          keys:
            - go-modules-{{ checksum "go.sum" }}

      - run: go mod download

      - save_cache:
          name: Save go modules cache
          key: go-modules-{{ checksum "go.sum" }}
          paths:
            - "/go/pkg/mod"

jobs:
  release:
    parameters:
      go-version:
        type: string

    executor:
      name: build
      go-version: << parameters.go-version >>

    steps:
      - checkout
      - go_mod_download
      - run:
          name: Run goreleaser
          command: curl -sL https://git.io/goreleaser | bash -s -- --rm-dist

workflows:
 release:
    jobs:
      - lint:
          go-version: "1.11.4"
      - release:
          go-version: "1.11.4"
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /[0-9]+(\.[0-9]+)(\.[0-9]+)/
```

上記によって、通常のブランチがプッシュされたときではなく、`git tag 1.0.x` のような tag がプッシュされた場合のみ、goreleaser が自動実行されるように設定しました。

設定してからは実際に fork したプロジェクトに CircleCI をインストールし、動作確認を進めました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/2a78f7df-499f-edac-9b69-0521ddb8a12e.png)

これで tag のプッシュ時のみ goreleaser が実行されるようになり、自分で goreleaser コマンドを実行しなくても良くなり、一通り検証が完了しました。

### プルリクエスト作成

今までに検証してきた内容を踏まえて、プルリクエストを作成しました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/6a18eacd-b139-e905-fdbe-3c98fc1e7876.png)

検証を通して、Homebrew は最新の master コミットからビルドするという形式ではなく、バージョンを切って使うのが一般的、ということを感じていました。

そのため、希望にそぐえないことを謝りつつ、最新バージョンを追従しやすい Homebrew Tap への自動生成に対応した、というプルリクエストを作成しました。

また、自分のアカウントに作った `homebrew-docui` をそのまま指定するよりも、オーナーである @gorilla0513 さんのリポジトリに新たに作成いただいたほうがユーザーの方々が迷わないと思い、その旨を書きました。

あとは自力で考えた案を落とし込んだプルリクエストだったので、理解していただきやすいよう、できる限り使い方の説明も入れ、疑問点があれば質問いただきたい、という旨のコメントも残しました。

### そしてマージへ

そしていくつかの質問のやりとりを経て、ついにマージのご連絡が！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/6d033cdf-247c-9570-6657-fe0c420637cb.png)

自分で考えたプルリクエストが受け入れられ、非常に嬉しかったです。対応ありがとうございました。

## 今回の経験を振り返って

今回の経験を経て、Issue に手を挙げて対応にチャレンジすると、以下を得られることがわかりました。

* できるかぎり良い対応をしようと考え・議論する機会が得られる
* Issue にチャレンジした結果、新たな知見と大きな達成感が得られる

### できるかぎり良い形の対応を考え・議論する機会が得られる

今回の経験を通して、OSS のプロジェクトへのプルリクエスト作成にチャレンジすると、より良い形の対応にするにはどうしたらいいかということを考え・議論する機会が得られるな、と思いました。

OSS のツールは全世界の人に利用される可能性があります。そのため、個人に閉じたプロジェクトとは違った、ユーザーやメンテナにとって使いやすい、より良い形にするにはどうすればいいか、ということを自然と考えるようになるのかな、と思いました。

もし自分でメンテナンスをしていくプロジェクトということであれば、goreleaser と CircleCI を使うという選択肢は思いつかなかったかもしれません。しかし今回携わった docui は個人に閉じておらず、注目を集めているプロジェクトでした。

そんなプロジェクトにおいて、より良い形を考え、議論した結果、Homebrew Tap を gorelaser と CircleCI で自動生成するという案に至れたのでは、と考えています。

### Issue にチャレンジした結果、新たな知見と大きな達成感が得られる

文章にするとごく当たり前のことかもしれないのですが、上記も強く感じました。

今回の Issue は未経験の技術を扱うこともあり、どんなプルリクエストにするのか、技術検証から行っていました。

その際、技術検証や公式ドキュメントを読み込んだ上でプルリクエストを作成する、ということを通じて、以下の新たな知見を得ることが出来ました。

* Homebrew Tap の使い方
* CircleCI を GitHub プロジェクトで設定する方法
* gorelaser の使い方
* gorelaser を CircleCI と連携する方法

また今回は自力で考えた案を落とし込んだプルリクエストだったので、理解していただきやすくなるよう、説明をいれたり、質問してほしいというコメントを残したりしたのですが、このアクションは今後も活きるのでは、ということを学ぶことも出来ました。

そして、様々な試行錯誤を経て作成したプルリクエストがマージされたときの達成感はとても大きい、ということも知ることが出来て良かったです。

## おわりに

今回プルリクエストを作成するにあたって様々な試行錯誤をしたことは、非常に勉強になりました。

未経験の事を対応するのは大変でしたが、その分得ることも多かったと思います。

引き続き機会があればプルリクエスト作成にチャレンジしてみようと思います！