Apple Silicon MacのHomebrew混在問題: 切り分けと最小構成
Apple Silicon(M1 / M2 / M3 など)の Mac で作業しているのに、
-
archはarm64なのに -
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
典型的に混ざっているとこんな感じになります:
-
arch→arm64 -
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つだけです。
-
.zshrcのexport PATH="...固定..."を削除/コメントアウト -
.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