🍺

Apple Silicon MacのHomebrew混在問題: 切り分けと最小構成

に公開

Apple Silicon(M1 / M2 / M3 など)の Mac で作業しているのに、

  • archarm64 なのに
  • which brew/usr/local/bin/brew を返してしまう

みたいな状態に遭遇すると、ライブラリや CLI が 意図せず x86_64(Rosetta)側に入ることがあり、特にネイティブ拡張を含むツールチェーン(例:OpenMP / libomp、Python拡張、C/C++ビルド)がハマりやすいです。

この記事では、混在の原因と直し方、そして **zsh(.zprofile / .zshrc)の「最小の完成形」**まで落とし込みます。


何が起きているのか:arm64 と x86_64(Rosetta)"2つの世界"

Apple Silicon Mac は CPU 自体は arm64 ですが、Intel 向け(x86_64)アプリも Rosetta 2 によって動きます。

Homebrew もこの「2つの世界」を分けて共存できるようにするため、インストール先(prefix)が分かれます。

アーキテクチャ Homebrew prefix
arm64(ネイティブ) /opt/homebrew
x86_64(Rosetta) /usr/local

混乱の本質は、PATH の順番次第で、arm64 シェルからでも /usr/local 側の brew を普通に呼べてしまうことです(必要なら Rosetta が介在して動いてしまう)。


症状の確認:まずは "今どっち?" を数秒で見る

arch
which -a brew
brew --prefix

典型的に混ざっているとこんな感じになります:

  • archarm64
  • which brew/usr/local/bin/brew(Intel 側)
  • brew --prefix/usr/local

原因の特定:ほぼ PATH 上書きが犯人

1) arm64 brew が存在するかチェック

ls -l /opt/homebrew/bin/brew
/opt/homebrew/bin/brew --prefix

これが存在して /opt/homebrew と出るなら、arm64 brew は入っています。

2) PATH の先頭20個を見る

echo $PATH | tr ':' '\n' | nl -ba | sed -n '1,20p'
which -a brew

ここで /usr/local/bin/opt/homebrew/bin より前に出ていたら、ほぼそれが原因です。

3) zsh の設定ファイルで "PATH の丸ごと上書き" を探す

grep -n "brew shellenv" ~/.zprofile ~/.zshrc 2>/dev/null
grep -n "export PATH=" ~/.zshrc 2>/dev/null

今回ハマりがちなパターンはこれ:

export PATH="/usr/local/bin:/System/....:/usr/bin:/bin:..."

この1行が それまで積み上げた PATH(.zprofile で入れた /opt/homebrew など)を全部捨てて、固定文字列で上書きしてしまいます。


解決:最小修正で直す(おすすめ)

やることは2つだけです。

  1. .zshrcexport PATH="...固定..." を削除/コメントアウト
  2. .zshrc の一番最後に "brew shellenv を入れ直す" を追加して 必ず勝たせる

「最小の完成形」:.zprofile と .zshrc(コピペOK)

ここからがこの記事のゴールです。

✅ ~/.zprofile(ログインシェル用)

# --- Homebrew: choose by architecture ---
if [[ "$(arch)" == "arm64" ]]; then
  if [[ -x /opt/homebrew/bin/brew ]]; then
    eval "$(/opt/homebrew/bin/brew shellenv)"
  fi
else
  # Rosetta (x86_64) session
  if [[ -x /usr/local/bin/brew ]]; then
    eval "$(/usr/local/bin/brew shellenv)"
  fi
fi

ポイント:

  • arm64 セッションなら /opt/homebrew
  • x86_64(Rosetta)セッションなら /usr/local
  • **"両方 eval しない"**のが混在防止に効きます

✅ ~/.zshrc(対話シェル用)

.zshrc では PATH を固定文字列で丸ごと上書きしないのが鉄則です。
そして、他の設定が PATH を触っても最後に arm64 brew を勝たせるために、末尾に同じブロックを置きます。

# --- (中略) いつもの設定たち ---

# ❌ これをやらない(今回の犯人になりやすい)
# export PATH="/usr/local/bin:...固定のPATH..."

# --- Homebrew: ensure correct one wins (put at the very end) ---
if [[ "$(arch)" == "arm64" ]]; then
  [[ -x /opt/homebrew/bin/brew ]] && eval "$(/opt/homebrew/bin/brew shellenv)"
else
  [[ -x /usr/local/bin/brew ]] && eval "$(/usr/local/bin/brew shellenv)"
fi

.zprofile に入れているなら .zshrc は不要では?と思うかもしれませんが、Terminal/iTerm2 の設定や起動形態によって .zprofile が走らないケースがあるので、**"最後に勝たせる保険"**として .zshrc 末尾にも置くのが安定です。


反映(確実なのはシェル再ログイン)

exec zsh -l

またはターミナルを完全に閉じて開き直し。


直ったか確認(期待値つき)

arch
which -a brew
brew --prefix

期待値(arm64 のとき):

arm64
/opt/homebrew/bin/brew
/usr/local/bin/brew
/opt/homebrew

この形になっていれば、「普段は arm64 brew」「必要なら x86_64 brewも残ってる」状態でかなり理想形です。


"二刀流"運用のコツ:必要なときだけ x86_64 を明示

普段は arm64 の世界で作業し、x86_64 が必要なときだけ明示的に実行するのが混乱しません。

# arm64(通常)
/opt/homebrew/bin/brew install <pkg>

# x86_64(必要時のみ)
arch -x86_64 /usr/local/bin/brew install <pkg>

実践:gogcli を arm64 brew で入れる例

直った状態なら、そのまま arm64 brew で入れればOKです。

brew update
brew tap steipete/tap
brew install steipete/tap/gogcli

確認:

which gog
file "$(which gog)"
gog --version

おまけ:PATH を綺麗にしたい人向け(任意)

いろんな言語環境(Go / cabal / ghcup / volta / .local など)を使うと PATH が増えがちです。
固定文字列で export PATH=… を作るより、zsh の path 配列で「足す」運用が安全です。

例(必要なものだけ残して調整):

# zsh: path 配列で管理(重複排除)
typeset -U path PATH
path+=(
  "$HOME/.local/bin"
  "$HOME/go/bin"
  "/usr/local/go/bin"
  "$HOME/.cabal/bin"
  "$HOME/.ghcup/bin"
  "$HOME/.volta/bin"
)
export PATH

ただし「順番に意味がある」ツールもあるので、ここは最小修正が済んでから、落ち着いて整えるのがおすすめです。


参考リンク

Discussion