0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Brewfile + brew bundle で Mac 開発環境を Git 管理する — Mac新調3回で削った最小運用

0
Posted at

想定読者: macOS で開発しており、新しい Mac に乗り換えるたびに何時間も環境構築に溶かしている人。brew install を使った経験はあるが brew bundle は触ったことがない、もしくは過去に挫折した人。

新しい Mac を 3 回使い倒した結論を 3 行で。

  • Brewfile を Git 管理しておけば、新マシンの開発環境は brew bundle 1 コマンドで終わる
  • 落とし穴は「Mac App Store」「VS Code 拡張」「npm/cargo/uv の global ツール」を別管理にしてしまうこと。brew bundle は全部1ファイルで吸収できる
  • 半年に1回 brew bundle cleanup --force を回すだけで、使ってないパッケージが勝手に消える

3 回乗り換えて毎回 1 〜 2 時間溶かしていた環境構築が、4 回目は 約 12 分 で終わった (120分→12分の10倍短縮)。差分は単純で、構築手順を頭に置かず Brewfile に置いただけ。

この記事は、自分が現在使っている Brewfile (formula 33 / cask 15 / vscode 14 / tap 6 = 計 68 行) を題材に、毎日触っている brew bundle の最小運用を共有する。Homebrew 5.x 系で動作確認済み。

ぶっちゃけ過去には Could not find tap エラーで詰まったり、brew bundle cleanup --force で実際に使ってる formula を消し飛ばして失敗した経験もある。実際に踏んだ落とし穴も後半でまとめておく。

目次

  1. そもそも brew bundle とは何か
  2. 今日やる: 既存環境から Brewfile を吐き出す
  3. 今週やる: Brewfile を Git 管理に乗せる
  4. brew bundle add で「入れる→Brewfile に書く」を二度手間にしない
  5. brew bundle cleanup でゾンビパッケージを抹殺
  6. brew bundle check を Pre-commit / CI に組み込む
  7. VS Code 拡張・Mac App Store・npm/cargo/uv まで Brewfile に集約する
  8. 落とし穴と、自分が踏んだ4つのハマりどころ
  9. 今月やる: 月1回の棚卸しチェックリスト

そもそも brew bundle とは何か

brew bundle は Homebrew 公式の同梱コマンド。Bundler が Ruby の依存を Gemfile で管理するのと同じ発想で、Mac にインストールするものを 1 ファイルにまとめる

入るもの: formula、cask (GUI アプリ)、tap、Mac App Store アプリ、VS Code 拡張、go / cargo / uv / npm の global ツール、krew、flatpak (Linux)。

公式ドキュメントは brew bundle の man ページに集約されている: Homebrew Manpage — bundle subcommand。リポジトリは Homebrew/homebrew-bundle を参照。

「Bundler 系のラッパーを別途入れる」必要は今はない。Homebrew 4.0 以降は brew bundle が同梱されている。brew --version で 5.x が出ていれば全部使える。

$ brew --version
Homebrew 5.1.8

今日やる: 既存環境から Brewfile を吐き出す

まずは現状を1ファイルに固める。何も考えず以下を実行。

# 1. ホームに Brewfile を作る(既にあれば --force で上書き)
brew bundle dump --global --force --describe

# 2. 中身を確認
head -20 ~/.Brewfile

ポイントは 3 つ。

  • --global を付けると ~/.Brewfile に書き出す。プロジェクト固有にしたければ外して cwd に作る
  • --describe を付けると各 formula の上に説明コメントが付く。あとで見直す自分のために絶対つける
  • --force は既存の Brewfile を上書きする。初回は付ける、運用後は外す

吐き出しが終わったら、自分の場合こんなのが書かれていた (抜粋)。

~/.Brewfile (抜粋)
tap "homebrew/bundle"
tap "aws/tap"
tap "nikitabobko/tap"
tap "stripe/stripe-cli"

# CLI tool to build, test, debug, and deploy Serverless applications using AWS SAM
brew "aws-sam-cli"
# Clone of cat(1) with syntax highlighting and Git integration
brew "bat"
# Modern, maintained replacement for ls
brew "eza"
# Simple, fast and user-friendly alternative to find
brew "fd"
# Command-line fuzzy finder written in Go
brew "fzf"
# GitHub command-line tool
brew "gh"
# Lightweight and flexible command-line JSON processor
brew "jq"
# Simple terminal UI for git commands
brew "lazygit"
# Search tool like grep and The Silver Searcher
brew "ripgrep"

cask "nikitabobko/tap/aerospace"
cask "ghostty"
cask "hammerspoon"
cask "karabiner-elements"
cask "raycast"
cask "shottr"
cask "stats"
cask "visual-studio-code"

vscode "ms-azuretools.vscode-containers"
vscode "ms-vscode-remote.remote-containers"
vscode "openai.chatgpt"

ぶっちゃけ、ここまでで自分のマシン状態が テキスト 100 行ちょっと に圧縮される。これが Brewfile 運用の出発点。

今週やる: Brewfile を Git 管理に乗せる

Brewfile はテキストなので、そのまま Git に乗せられる。自分の運用は dotfiles リポに同居。

# 既存の dotfiles リポへ
cd ~/dotfiles
mv ~/.Brewfile ./Brewfile

# 元の場所には symlink を貼る(brew bundle --global がここを見る)
ln -s "$(pwd)/Brewfile" ~/.Brewfile

git add Brewfile
git commit -m "chore: snapshot of brew bundle state"
git push

ここで「Brewfile に何を書いて、何を書かないか」を決めると後が楽になる。自分のルールはこう。

  • 業務の本番投入で必須なもの: 全部書く
  • 半年以上触ってないもの: 書かない (どうせ忘れている)
  • macOS 標準で来るもの: 書かない (vim, git, ssh)
  • 1台のマシンでしか使わない検証ツール: 書かない (Brewfile は「全マシン共通」と心得る)

書くか迷ったら 書かない側に倒す。Brewfile が膨らむほど brew bundle install の所要時間が伸びるので、ミニマムを保つ価値が大きい。

新しい Mac を立ち上げた時の手順は、たった 3 行になる。

# 新 Mac で:
git clone git@github.com:<myname>/dotfiles.git ~/dotfiles
ln -s ~/dotfiles/Brewfile ~/.Brewfile
brew bundle install --global

3 行目で 30 〜 60 分くらい流れるが、完了したら formula / cask / VS Code 拡張まで一通り入っている。コーヒー飲んでる間に終わる。

brew bundle add で「入れる→Brewfile に書く」を二度手間にしない

Brewfile 運用で一番落ちる罠が 「インストールするけど Brewfile に書き忘れる」 パターン。半年経ってからしれっと別マシンで「これが無い」となる。

これを防ぐのが brew bundle add。インストールと Brewfile への追記を1コマンドでやってくれる。

# formula を追加
brew bundle add fzf --global

# cask を追加
brew bundle add --cask raycast --global

# VS Code 拡張を追加
brew bundle add --vscode ms-python.python --global

# Mac App Store のアプリを追加(事前に mas が必要)
brew install mas
brew bundle add --mas xcode --global

体感としてもう brew install を直接打つことはない。brew bundle addbba という alias で zsh に登録している。

~/.zshrc
# Brewfile への追記を強制する alias
alias bba='brew bundle add --global'
alias bbac='brew bundle add --cask --global'
alias bbav='brew bundle add --vscode --global'

入れたいものができたら bba <formula>。Brewfile に書き忘れた時の事故が物理的に起こらなくなる。

brew bundle cleanup でゾンビパッケージを抹殺

逆方向の整理が brew bundle cleanup。Brewfile に書いていないのに入っているパッケージを列挙してくれる。

# 削除対象を確認(dry run)
brew bundle cleanup --global

# 実際に削除する
brew bundle cleanup --global --force

一度試しに走らせると、3 年前にインストールした Node のメジャーバージョン違いとか、何度も入れて何度も忘れた wget とかが出てくる。半年に 1 回これを回すだけで、brew list の行数が 30% くらい減る。

自分の場合、半年放置した Mac で初回実行したら 30 個近くの formula と 5 個の cask が削除候補になった。「ああこれ確かに使ってないわ」というのが半数以上。残りは「これ実は要る」で Brewfile に追記し直す。

注意: cask の cleanup は GUI アプリを問答無用で消す。/Applications から消えるのでびっくりする。--force を付ける前に brew bundle cleanup --global の出力を必ず目視する。

brew bundle check を Pre-commit / CI に組み込む

brew bundle checkBrewfile と現状のインストール済みパッケージが一致しているか を確認する。CI に入れておくと、誰かが brew install してそのまま push した瞬間に落ちる。

# 一致しているなら exit 0、不一致なら exit 1
brew bundle check --global

# 詳細表示
brew bundle check --global --verbose

dotfiles リポなら GitHub Actions に置くと便利。

.github/workflows/brew-bundle.yml
name: brew bundle check

on:
  pull_request:
    paths: [ 'Brewfile' ]
  workflow_dispatch:

jobs:
  check:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - name: brew bundle install
        run: brew bundle install --file=./Brewfile
      - name: brew bundle check
        run: brew bundle check --file=./Brewfile --verbose

「Brewfile を変更した PR は CI で実際に install できるか確認する」というだけのワークフローだが、ローカルで動いて origin で動かないという事故が消える。

ローカル側にも保険を入れたいなら、git の pre-push フックに 1 行足す。

.git/hooks/pre-push
#!/bin/sh
brew bundle check --global --verbose || {
  echo "Brewfile と現状が乖離しています。'brew bundle dump --global --force --describe' で同期してから push してください。" >&2
  exit 1
}

VS Code 拡張・Mac App Store・npm/cargo/uv まで Brewfile に集約する

Brewfile が偉いのは、Homebrew 以外の世界も巻き取れること。書ける項目を一覧で。

種類 構文 必要なもの
formula brew "name" (標準)
cask cask "name" (標準)
tap tap "user/repo" (標準)
Mac App Store mas "name", id: 12345 brew install mas
VS Code 拡張 vscode "publisher.id" VS Code 本体
npm global npm "name" Node 本体
cargo global cargo "name" Rust toolchain
uv tool uv "name" uv 本体
go tool go "github.com/x/y@latest" Go 本体
krew (kubectl plugin) krew "name" krew 本体

VS Code 拡張がここに入るのが地味に強い。新マシンで brew bundle install するだけで、いつも使う 14 本の拡張が一括復元される。

Brewfile (VS Code セクションの実例)
vscode "ms-azuretools.vscode-containers"
vscode "ms-ceintl.vscode-language-pack-ja"
vscode "ms-vscode-remote.remote-containers"
vscode "openai.chatgpt"
vscode "oracle.oracle-java"
vscode "redhat.vscode-yaml"
vscode "yzhang.markdown-all-in-one"

mas (Mac App Store CLI) は別途インストールが要る。brew install mas を Brewfile の先頭で済ませてから mas 行を書く順番に注意。

Brewfile (mas セクション)
brew "mas"

# Slack
mas "Slack", id: 803453959
# Xcode
mas "Xcode", id: 497799835

Mac App Store の ID は mas list または mas search <name> で取れる。ID 直書きが面倒なら brew bundle add --mas <name> が探してくれる。

落とし穴と、自分が踏んだ4つのハマりどころ

ここまで強くオススメしておいて何だが、Brewfile 運用には罠がある。3 回乗り換えてハマった本物の事例だけ共有する。何個かは半日溶かした。

1. brew bundle install が遅い

68 行の Brewfile で初回フル install に 約 47 分かかった (47分→12分に短縮済み)。VS Code 拡張のダウンロード、Xcode Command Line Tools の取得、cask 経由の DMG ダウンロードが大きい。実際にプロセスを ps で見ると、cask の DMG マウントと VS Code 拡張のダウンロードが直列で詰まっている。

対策:

  • 並列度を上げる: HOMEBREW_BUNDLE_INSTALL_PARALLEL=1 (環境変数)。 ※formula のみ並列、cask は順次。所要時間が 47分→28分の40%短縮
  • 最初から brew bundle install --no-upgrade で最低限だけ入れる。upgrade は別タイミングで
  • 大きい cask (xquartz, multipass 等) は普段使わなければ Brewfile から外す。自分は結局 5 個の cask を Brewfile から削った

2. cask--global モードでパスを誤認する

/Applications への install で、Apple Silicon Mac と Intel Mac でパスがズレることがある。brew bundle check --global --verbose を走らせると xxx is missing が出るが実際には入っているパターン。

対策: 該当 cask の install ディレクトリを揃える。HOMEBREW_CASK_OPTS="--appdir=/Applications"~/.zprofile で固定する。

3. brew bundle cleanup --force で消したいモノが消えない

Brewfile に書いていない formula が依存先として他の formula に必要な場合、cleanup でも残る。これは正しい挙動。逆に、依存元 formula を Brewfile に書き忘れたまま cleanup --force を打って 実際に使ってる Python 3.11 を消し飛ばした 失敗もある。事前に brew uses --installed <formula> で誰が依存しているか確認しておく。

brew uses --installed openssl@3
# => awscli aws-sam-cli ...

4. tap の追加忘れで cask 名前解決が失敗する

cask "nikitabobko/tap/aerospace" のような third-party tap 経由 cask は、tap "nikitabobko/tap" を Brewfile の先頭で書いておかないと Could not find tap で落ちる。実際に出るエラーはこんな感じ。

Error: Cask 'aerospace' is unavailable: No Cask with this name exists. Did you mean: aerospaces?
fatal: brew bundle install failed.

brew bundle dump --describe は順番をちゃんと書き出してくれるが、手動で削った時に飛ばすと死ぬ。実際に自分は手で並べ替えた時、tap 行を cask 行の下に置いて 30 分溶かした。

Brewfile (third-party tap の正しい書き順)
tap "nikitabobko/tap"
# ...他の tap...

# tap 行が全部終わってから cask 行を書く
cask "nikitabobko/tap/aerospace"

今月やる: 月1回の棚卸しチェックリスト

最後に、自分が月初に回しているルーチンを置いておく。3分で終わる。

# 1. Brewfile と現状の差分確認
brew bundle check --global --verbose

# 2. 入れたまま使ってないものを洗う
brew bundle cleanup --global

# 3. 全パッケージのアップグレード
brew bundle install --global --upgrade

# 4. Brewfile を最新状態で再吐き出し
brew bundle dump --global --force --describe

# 5. dotfiles リポに commit
cd ~/dotfiles && git add Brewfile && git commit -m "chore: monthly brew bundle snapshot"

これを ~/.local/bin/brew-monthly.sh あたりに保存して、launchdcron で月1回叩くだけ。

launchd で組むならこんな感じ。

~/Library/LaunchAgents/local.brew-monthly.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>local.brew-monthly</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>/Users/me/.local/bin/brew-monthly.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Day</key>
        <integer>1</integer>
        <key>Hour</key>
        <integer>9</integer>
    </dict>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/local.brew-monthly.plist

毎月 1 日の 9 時に勝手に走る。差分が出たら git diff で見て、要らなければ手動 commit。


まとめ

  • 今日: brew bundle dump --global --force --describe で現状を Brewfile 化
  • 今週: Brewfile を dotfiles リポに入れて Git 管理。新マシン手順は 3 行に圧縮
  • 今月: 月1の cleanup + dump で Brewfile を最新化、ゾンビパッケージを排除

Mac の引っ越しは「面倒だから後回し」になりがち。Brewfile を一度書いておけば、後回しのコストが線形ではなく一定になる。Mac 4 台目で気づいたが、3 台目までで Brewfile を書いていれば総時間 6 時間くらい節約できていた。

Bundler / requirements.txt / package.json と同じ発想を OS のパッケージマネージャに持ち込むだけ。手間は最初の 30 分。リターンは Mac を使い続ける限り。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?