Homebrewの内部動作を理解する

これは「Happiness Chain Advent Calendar 2023」の23日目の記事です。
https://adventar.org/calendars/9414/

## はじめに

みなさんお世話になっているHomebrewですが、内部動作の理解を後回しにして使っている方もいるのではないでしょうか。
もちろん私もそうでしたが、先日躓いた際に基本動作から調べる機会がありました。
基本動作を理解することでエラーも解決できましたし、何よりも前よりHomebrewを気持ちよく使うことができるようになりました。
これを読んだ方が同じ効果を得られるように記事にまとめてみました。
特に`brew install`を実行してからインストールしたものを実行できるところに焦点を当てました。

## 内容

### 動作環境

- Apple M1
- macOS 14.1.2
- Homebrew 4.1.25-42-gcd8a9c0


### 用語

まずHomebrewで出てくる用語について簡単に触れておきます。

| 用語      | 原訳     | homebrew上での意味                     |
| -------- | -------- | -------------------------------------- |
| Homebrew | 自家製ビール    | ツール名そのもの                       |
| brew     | 醸造する  | コマンド                               |
| formula  | 製法     | インストール手順を書いたRubyスクリプト |
| keg      | 小樽     | インストールした実行ファイル置き場               |
| cellar   | 貯蔵庫   | kegをまとめて管理するディレクトリ      |

### 大まかな流れ

`brew install`実行時の大まかな流れは下記になります

- formulaを取得
- 取得したformulaを元にインストールした実行ファイルをkegに保存
- kegへのエイリアスを作成
- エイリアスにpathを通すことでシェルから呼び出せるようになる

以降で流れを追っていきます。

### formulaを取得してインストール

以降は
```sh
$ brew install tree
```
の実行を例にします。
実行するとまず、インストール方法が書かれたformulaを取得しにいきます。
formulaはGitHub上に置かれており、treeの場合は下記のアドレスになります。
https://github.com/Homebrew/homebrew-core/blob/master/Formula/t/tree.rb
ちなみにインストール後は`brew cat tree`でもformulaの中身を確認できます。

```ruby
class Tree < Formula
  desc "Display directories as trees (with optional color/HTML output)"
  homepage "http://mama.indstate.edu/users/ice/tree/"
  url "http://mama.indstate.edu/users/ice/tree/src/tree-2.1.1.tgz"
  sha256 "d3c3d55f403af7c76556546325aa1eca90b918cbaaf6d3ab60a49d8367ab90d5"
  license "GPL-2.0-or-later"

  livecheck do
    url "http://mama.indstate.edu/users/ice/tree/src/"
    regex(/href=.*?tree[._-]v?(\d+(?:\.\d+)+)\.t/i)
  end

  def install
    ENV.append "CFLAGS", "-fomit-frame-pointer"
    objs = "tree.o list.o hash.o color.o file.o filter.o info.o unix.o xml.o json.o html.o strverscmp.o"

    system "make", "PREFIX=#{prefix}",
                   "MANDIR=#{man}",
                   "CC=#{ENV.cc}",
                   "CFLAGS=#{ENV.cflags}",
                   "LDFLAGS=#{ENV.ldflags}",
                   "OBJS=#{objs}",
                   "install"
  end

  test do
    system "#{bin}/tree", prefix
  end
end
```

実際のインストール方法はコンパイル、バイナリ取得、インストールスクリプトなどがありますが、これをスクリプトで実行してくれているのでユーザーは`brew install`のコマンドを実行するだけで良いのです。
ちなみにインストール物がバイナリの場合はbottleと呼ばれます。
また、macOSのGUIアプリの場合はcask(大樽)と呼ばれます。以前はinstall時に`--cask`オプションが必要でしたが、今は不要になりました。

### 保存場所とパスの確認

先ほどの`brew install tree`のコマンドでインストールが完了しました。
treeの実行可能ファイルやライブラリの実態はcellarの中のkegにインストールされます。
どこにあるか探してみましょう。
まずtreeがどこを示すかコマンドで確認してみます。

```sh
$ which tree
/opt/homebrew/bin/tree
```

ここがkegでしょうか？違います。詳細を見てみます。

```sh
$ ls -l $(which tree)
lrwxr-xr-x  1 takuzo  admin  29  9 22 04:13 /opt/homebrew/bin/tree -> ../Cellar/tree/2.1.1/bin/tree
```

`/opt/homebrew/bin/tree`はシンボリックリンクであることが分かります。
これはインストール時に作成されたものです。
そしてリンク先の`/opt/homebrew/Cellar/tree/2.1.1/bin/tree`がkegでここに実行ファイルが置かれます。(実はインストール実行時のログにも書かれています)

実際に使われる際は`/opt/homebrew/bin`に置かれたシンボリックリンクへアクセスされます。
homebrewをインストールした際にここへのパスが通されており、`echo $PATH`で確認できます。

```sh
$ echo $PATH | grep '\/opt\/homebrew\/bin'
..前略..:/opt/homebrew/bin:..後略..
```

またcellarは下記のコマンドで所在を確認できます。

```sh
brew --cellar
/opt/homebrew/Cellar
```

以上をまとめると、`brew install XXXX`を実行した際はCellarの中に実行ファイルが置かれ、コンソールからコマンドを実行したときは下記の流れで実行ファイルが呼び出されます。

1. $PATH
2. /opt/homebrew/bin/XXXX
3. /opt/homebrew/Cellar/XXXX/X.X.X/bin/XXXX


#### 補足：CPUによる保存場所の違い

ちなみにMacのintelコア(x86_64)では/usr/local/binに置かれていましたが、M1チップ(arm64)以降は/opt/homebrew/binに置かれるようになりました。
その他のvarやetcもintelコアではrootに置かれますがM1以降は/opt/homebrew配下へ置かれるようになりました。
古い記事を読んだ際は置き場が異なるので注意してください。

#### 補足：リンクについて

上記で説明したようにインストールされると自動でリンクが貼られますが、手動でリンクを貼る必要がある場合があります。
openssl, node, mysqlなど複数のバージョンのkegがcellarの中に存在する場合や、マニュアルでインストールした場合です。
その場合は`brew link --force openssl@1.1`のようにするとリンクが貼られます。
（本来はコンテナやasdfなどで管理するべきですが。。。）
参考にbrew linkのコマンドヘルプも載せておきます。

```sh
brew link -h
Usage: brew link, ln [options] installed_formula [...]

Symlink all of formula's installed files into Homebrew's prefix.
This is done automatically when you install formulae but can be useful for manual installations.
```

### おわりに

Homebrewでインストールしてから使えるようになるまでの流れを確認できました。
これで以前よりも気持ちよく`brew install`コマンドを実行できるようになったのではないでしょうか。
また、もし環境構築などでトラブルがあった時はこのことを覚えていると早く解決できるようになると思います。

#### 参考

記事作成にあたり、下記を参考にさせていただきました！

- https://docs.brew.sh/Manpage
- https://blog.ottijp.com/2020/05/23/homebrew/