投稿

Homebrew の pyenv/rbenv/goenv/nodenv/tfenv を anyenv に一本化する移行手順

Homebrew の pyenv/rbenv/goenv/nodenv/tfenv を anyenv に一本化する移行手順

TL;DR

Homebrew で個別にインストールした pyenv, rbenv, nodenv, goenv, tfenv を anyenv に統合する。シェル設定がスッキリし、brew upgrade による依存関係破壊(expat 問題など)も回避できる。

背景: なぜ anyenv に移行するのか

Homebrew の brew upgrade で Python が壊れた。原因は expat ライブラリのバージョン不整合:

ImportError: Symbol not found: _XML_SetAllocTrackerActivationThreshold
  Referenced from: pyexpat.cpython-313-darwin.so
  Expected in: /usr/lib/libexpat.1.dylib

Homebrew は Python を新しい expat ヘッダでビルドするが、実行時はシステムの古い /usr/lib/libexpat.1.dylib をロードする。pyenv でソースビルドすれば expat を静的リンクするのでこの問題が起きない。

ついでにバラバラだった *env ツール群を anyenv で一元管理することにした。

移行前の状態

全て Homebrew でインストール。.zshenv.zshrc に個別の PATH/init 設定が散らばっている:

# .zshenv(移行前)
export PATH="$HOME/.rbenv/bin:$PATH"
export PATH="$HOME/.nodenv/bin:$PATH"
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
export GOENV_ROOT="$HOME/.goenv"
export PATH="$GOENV_ROOT/bin:$PATH"

# .zshrc(移行前)
eval "$(rbenv init - --no-rehash)"
eval "$(nodenv init - --no-rehash)"
eval "$(pyenv init - --no-rehash zsh)"
eval "$(goenv init - --no-rehash)"

手順

1. anyenv をインストール

git clone https://github.com/anyenv/anyenv ~/.anyenv
export PATH="$HOME/.anyenv/bin:$PATH"
anyenv install --force-init

2. anyenv 経由で各 *env をインストール

anyenv install pyenv
anyenv install rbenv
anyenv install nodenv
anyenv install goenv
anyenv install tfenv

各ツールは ~/.anyenv/envs/<name>/ 以下にクローンされる。

3. 既存の versions を移行

Homebrew 版が ~/.<name>/versions/ に保存していた言語バージョンを、anyenv 配下にコピーする:

# pyenv
mkdir -p ~/.anyenv/envs/pyenv/versions
cp -a ~/.pyenv/versions/* ~/.anyenv/envs/pyenv/versions/
cp ~/.pyenv/version ~/.anyenv/envs/pyenv/version

# rbenv
mkdir -p ~/.anyenv/envs/rbenv/versions
cp -a ~/.rbenv/versions/* ~/.anyenv/envs/rbenv/versions/
cp ~/.rbenv/version ~/.anyenv/envs/rbenv/version

# goenv
mkdir -p ~/.anyenv/envs/goenv/versions
cp -a ~/.goenv/versions/* ~/.anyenv/envs/goenv/versions/
cp ~/.goenv/version ~/.anyenv/envs/goenv/version

# nodenv
mkdir -p ~/.anyenv/envs/nodenv/versions
cp -a ~/.nodenv/versions/* ~/.anyenv/envs/nodenv/versions/
cp ~/.nodenv/version ~/.anyenv/envs/nodenv/version

tfenv は Homebrew Cellar に versions が格納されているので注意:

mkdir -p ~/.anyenv/envs/tfenv/versions
cp -a /opt/homebrew/Cellar/tfenv/*/versions/* ~/.anyenv/envs/tfenv/versions/
cp /opt/homebrew/Cellar/tfenv/*/version ~/.anyenv/envs/tfenv/version

4. シェル設定を書き換え

.zshenv:

# anyenv(個別の *env PATH 設定を全て置き換え)
export ANYENV_ROOT="$HOME/.anyenv"
export PATH="$ANYENV_ROOT/bin:$PATH"

.zshrc:

# anyenv(個別の *env init を全て置き換え)
if command -v anyenv >/dev/null 2>&1; then
    eval "$(anyenv init - --no-rehash)"
fi

5. 旧パスへのシンボリックリンクを作成

これが重要。 Ruby や Python のコンパイル済みバイナリは、ビルド時の絶対パス(例: ~/.rbenv/versions/2.7.8/lib/libruby.2.7.dylib)を rpath としてハードコードしている。cp でファイルを移動しても rpath は書き換わらないので、旧パスから anyenv 配下へシンボリックリンクを張る必要がある:

# 旧ディレクトリをバックアップ
for d in .pyenv .rbenv .goenv .nodenv; do
  mv ~/$d ~/${d}.bak
done

# 旧パス → anyenv 配下にシンボリックリンク
for name in pyenv rbenv goenv nodenv; do
  ln -s ~/.anyenv/envs/$name ~/.$name
done

これがないと以下のようなエラーになる:

dyld: Library not loaded: /Users/<user>/.rbenv/versions/2.7.8/lib/libruby.2.7.dylib
  Referenced from: /Users/<user>/.anyenv/envs/rbenv/versions/2.7.8/bin/ruby
  Reason: tried: '/Users/<user>/.rbenv/versions/2.7.8/lib/libruby.2.7.dylib' (no such file)

6. Homebrew の *env パッケージを削除

brew uninstall pyenv rbenv nodenv goenv tfenv

7. 動作確認

新しいターミナルを開いて:

anyenv versions
# → 全ツール・全バージョンが表示される

python --version   # pyenv 経由
ruby --version     # rbenv 経由
node --version     # nodenv 経由
go version         # goenv 経由
terraform version  # tfenv 経由

移行後の pyenv で Python 3.13 を追加

pyenv install 3.13.13
cd /path/to/project
pyenv local 3.13.13    # .python-version が生成される
rm -rf .venv
python -m venv .venv
.venv/bin/pip install -r requirements.txt

pyenv ビルドの Python は expat を静的リンクするので、Homebrew 版で起きた pyexpatSymbol not found エラーは発生しない:

$ otool -L ~/.anyenv/envs/pyenv/versions/3.13.13/lib/python3.13/lib-dynload/pyexpat.cpython-313-darwin.so
# /usr/lib/libexpat.1.dylib がリンク先に含まれない → 静的リンク

後片付け

問題なく動作したら旧バックアップを削除:

rm -rf ~/.pyenv.bak ~/.rbenv.bak ~/.goenv.bak ~/.nodenv.bak

注意: ~/.pyenv, ~/.rbenv 等のシンボリックリンクは削除しないこと。コンパイル済みバイナリの rpath がこのパスを参照しているため、消すと dylib のロードに失敗する。新規にインストールしたバージョン(anyenv 配下でビルドされたもの)は anyenv のパスで rpath が設定されるので、将来的に旧バージョンを全て入れ直せばシンボリックリンクは不要になる。

この投稿は投稿者によって CC BY 4.0 の下でライセンスされています。

トレンドのタグ