T-CREATOR

Homebrew で複数バージョン共存:brew extract と brew link --overwrite 実践手順

Homebrew で複数バージョン共存:brew extract と brew link --overwrite 実践手順

macOS や Linux で開発をしていると、「このプロジェクトは Node.js v14 が必要だけど、別のプロジェクトでは v18 を使いたい」といった状況に遭遇することがあります。Homebrew では通常、最新バージョンのパッケージしかインストールできませんが、実は brew extractbrew link を組み合わせることで、複数のバージョンを共存させられるのです。

この記事では、Homebrew で特定バージョンのパッケージを抽出してインストールし、必要に応じてバージョンを切り替える実践的な手順を詳しく解説します。

背景

Homebrew の基本的なバージョン管理

Homebrew は macOS や Linux で広く使われているパッケージマネージャーで、コマンド一つでソフトウェアのインストールや更新が可能です。しかし、Homebrew の基本思想は「常に最新版を提供する」ことにあり、デフォルトでは古いバージョンを直接インストールする仕組みがありません。

そのため、プロジェクトごとに異なるバージョンが必要な場合、従来は以下のような課題がありました。

Homebrew のパッケージ管理の仕組み

以下の図は、Homebrew がパッケージをどのように管理しているかを示しています。

mermaidflowchart TB
  tap["Homebrew Tap<br/>(Formula リポジトリ)"]
  cellar["Cellar<br/>(/opt/homebrew/Cellar)"]
  link["シンボリックリンク<br/>(/opt/homebrew/bin)"]

  tap -->|Formula 取得| cellar
  cellar -->|brew link| link
  link -->|コマンド実行| user["ユーザー"]

  cellar -.->|複数バージョン| v1["package@1.0"]
  cellar -.->|格納可能| v2["package@2.0"]

この図が示すように、Homebrew は Formula(パッケージ定義ファイル)から Cellar にパッケージをインストールし、シンボリックリンクを作成してユーザーがコマンドとして実行できるようにしています。

なぜ複数バージョンの共存が必要なのか

開発現場では、以下のようなシーンで複数バージョンの共存が求められます。

  • レガシープロジェクトの保守: 古いバージョンの Node.js や Python が必要
  • 互換性テスト: 異なるバージョンでの動作確認
  • 段階的な移行: 新バージョンへの移行中に旧バージョンも並行利用
  • 依存関係の制約: 特定のライブラリが特定バージョンにしか対応していない

こうしたニーズに応えるため、Homebrew では brew extract という機能が提供されています。

課題

従来の方法の限界

Homebrew で古いバージョンをインストールしようとすると、以下のような問題に直面します。

1. 標準リポジトリには最新版しかない

通常の brew install では、最新版のみがインストール対象です。

bash# 最新版の Node.js がインストールされる
brew install node
bash# 特定バージョンを指定してもエラーになる
brew install node@14
# Error: No available formula with the name "node@14"

2. バージョン指定された Formula が限定的

一部のパッケージには @ 付きのバージョン指定 Formula がありますが、すべてのパッケージやバージョンで提供されているわけではありません。

bash# Python は一部バージョンが提供されている
brew install python@3.9

# しかし、すべてのバージョンがあるわけではない
brew install python@3.7
# Error: No available formula...

3. 手動でのバージョン管理が煩雑

以前は Git リポジトリから古い Formula を探し出して手動でインストールする方法もありましたが、非常に手間がかかります。

バージョン切り替え時の競合問題

複数バージョンをインストールした後も、以下のような課題があります。

mermaidflowchart LR
  v14["Node.js 14<br/>(Cellar 内)"]
  v18["Node.js 18<br/>(Cellar 内)"]
  link["シンボリックリンク<br/>(/opt/homebrew/bin/node)"]

  v14 -.->|競合| link
  v18 -.->|競合| link
  link --> user["ユーザー"]

  style link fill:#ffcccc

この図が示すように、複数バージョンが Cellar に存在しても、シンボリックリンクは 1 つしか作れないため、どちらかのバージョンしか有効化できません。

そこで必要になるのが、brew linkbrew unlink によるバージョン切り替えです。

解決策

brew extract の基本概念

brew extract は、Homebrew の公式リポジトリから特定バージョンの Formula を抽出し、自分専用の Tap(ローカルリポジトリ)に保存するコマンドです。

以下の図は、brew extract の仕組みを示しています。

mermaidflowchart TB
  official["公式 Tap<br/>(homebrew/core)"]
  history["Git 履歴<br/>(過去の Formula)"]
  local["ローカル Tap<br/>(homebrew/local)"]
  cellar["Cellar<br/>(実際のインストール先)"]

  official --> history
  history -->|brew extract| local
  local -->|brew install| cellar

  history -.->|例| v1["node 14.17.0 の Formula"]
  history -.->|例| v2["node 16.13.0 の Formula"]

この仕組みにより、公式リポジトリの過去の状態から必要なバージョンの Formula を取り出し、ローカル環境でインストール可能にします。

brew link は、インストール済みのパッケージにシンボリックリンクを作成し、コマンドとして実行可能にします。--overwrite オプションを付けることで、既存のリンクを強制的に上書きできます。

#コマンド説明用途
1brew linkシンボリックリンクを作成パッケージを有効化
2brew unlinkシンボリックリンクを削除パッケージを無効化
3brew link --overwrite既存リンクを上書きバージョン切り替え
4brew link --force警告を無視して強制リンクkeg-only パッケージの有効化

これらを組み合わせることで、複数バージョンを自在に切り替えられるようになります。

全体の作業フロー

以下は、複数バージョン共存の実現から切り替えまでの全体フローです。

mermaidflowchart TD
  start["開始"] --> tap["ローカル Tap を作成"]
  tap --> extract["brew extract で<br/>特定バージョンを抽出"]
  extract --> install["抽出した Formula を<br/>インストール"]
  install --> check["インストール確認"]
  check --> switch{"バージョン切り替えが<br/>必要?"}
  switch -->|はい| unlink["現在のバージョンを<br/>brew unlink"]
  unlink --> linkNew["新しいバージョンを<br/>brew link --overwrite"]
  linkNew --> verify["バージョン確認"]
  switch -->|いいえ| done["完了"]
  verify --> done

この流れを理解することで、作業の全体像が掴めます。それでは、具体的な手順を見ていきましょう。

具体例

ここでは、Node.js の複数バージョン(v14.17.0 と v18.12.0)を共存させ、切り替える手順を実践します。

ステップ 1: ローカル Tap の作成

まず、抽出した Formula を保存するためのローカル Tap を作成します。Tap は Homebrew が Formula を管理するリポジトリの単位です。

bash# ローカル Tap を作成(homebrew/local という名前)
brew tap-new homebrew/local

このコマンドを実行すると、以下のような出力が表示されます。

bash==> Created homebrew/local
/opt/homebrew/Library/Taps/homebrew/homebrew-local

ローカル Tap は ​/​opt​/​homebrew​/​Library​/​Taps​/​homebrew​/​homebrew-local に作成され、ここに Formula が保存されます。

ステップ 2: 特定バージョンの Formula を抽出

次に、brew extract を使って公式リポジトリから Node.js v14.17.0 の Formula を抽出します。

bash# Node.js 14.17.0 の Formula を抽出
brew extract --version=14.17.0 node homebrew/local

このコマンドの各パラメータは以下の意味を持ちます。

#パラメータ説明
1--version抽出するバージョン14.17.0
2nodeパッケージ名node
3homebrew​/​local保存先の Tap先ほど作成したローカル Tap

抽出が成功すると、以下のような出力が表示されます。

bash==> Searching repository history
==> Writing formula for node from revision 7a6f... to:
/opt/homebrew/Library/Taps/homebrew/homebrew-local/Formula/node@14.17.0.rb

これで、node@14.17.0 という名前の Formula がローカル Tap に作成されました。

ステップ 3: 抽出した Formula からインストール

抽出した Formula を使って、実際に Node.js v14.17.0 をインストールします。

bash# 抽出した Formula からインストール
brew install node@14.17.0

インストールには数分かかる場合があります。完了すると、以下のようなメッセージが表示されます。

bash==> Installing node@14.17.0 from homebrew/local
==> Downloading https://nodejs.org/dist/v14.17.0/node-v14.17.0.tar.gz
==> ./configure --prefix=/opt/homebrew/Cellar/node@14.17.0/14.17.0
==> make install
🍺  /opt/homebrew/Cellar/node@14.17.0/14.17.0: 3,000 files, 50MB

この時点で、Node.js v14.17.0 は Cellar にインストールされていますが、まだシンボリックリンクは作成されていません。

ステップ 4: 別バージョンも同様に抽出・インストール

同じ手順で、Node.js v18.12.0 もインストールします。

bash# Node.js 18.12.0 の Formula を抽出
brew extract --version=18.12.0 node homebrew/local
bash# インストール
brew install node@18.12.0

これで、2 つのバージョンが Cellar に共存している状態になりました。

ステップ 5: インストール確認

インストールされているバージョンを確認しましょう。

bash# インストール済みの Node.js 一覧を表示
brew list --versions | grep node

以下のような出力が表示されれば成功です。

bashnode@14.17.0 14.17.0
node@18.12.0 18.12.0

この時点では、まだどちらもリンクされていない(有効化されていない)状態です。

ステップ 6: バージョンの有効化

使いたいバージョンをリンクして有効化します。ここでは、まず v18.12.0 を有効化してみましょう。

bash# Node.js 18.12.0 をリンク(有効化)
brew link node@18.12.0 --overwrite
bash# バージョン確認
node --version

以下のように表示されれば、正しくリンクされています。

bashv18.12.0

ステップ 7: バージョンの切り替え

プロジェクトに応じてバージョンを切り替える手順を見ていきます。

現在のバージョンをアンリンク

まず、現在リンクされているバージョンを無効化します。

bash# 現在の Node.js をアンリンク
brew unlink node@18.12.0
bash# アンリンク確認
node --version

アンリンク後は、node コマンドが見つからないか、システム標準の Node.js が表示されます。

bash-bash: node: command not found
# または
v16.14.0  # システム標準の Node.js

別バージョンをリンク

次に、使いたいバージョン(v14.17.0)をリンクします。

bash# Node.js 14.17.0 をリンク
brew link node@14.17.0 --overwrite
bash# バージョン確認
node --version

以下のように表示されれば、切り替え成功です。

bashv14.17.0

ステップ 8: 競合解決(--overwrite の活用)

既存のシンボリックリンクがある状態で brew link を実行すると、以下のようなエラーが出る場合があります。

bashError: Could not symlink bin/node
Target /opt/homebrew/bin/node
already exists. You may want to remove it:
  rm '/opt/homebrew/bin/node'

このような場合、--overwrite オプションを付けることで強制的に上書きできます。

bash# 既存のリンクを上書きして強制的にリンク
brew link node@14.17.0 --overwrite --force

--force オプションは、keg-only(通常リンクされないパッケージ)を強制的にリンクする場合にも使用します。

複数バージョン切り替えフローの可視化

以下の図は、実際の切り替え作業の流れを示しています。

mermaidstateDiagram-v2
  [*] --> v18_linked: brew link node@18.12.0
  v18_linked --> unlinked: brew unlink node@18.12.0
  unlinked --> v14_linked: brew link node@14.17.0
  v14_linked --> unlinked: brew unlink node@14.17.0
  unlinked --> v18_linked: brew link node@18.12.0

  note right of v18_linked
    node --version
    → v18.12.0
  end note

  note right of v14_linked
    node --version
    → v14.17.0
  end note

  note right of unlinked
    node コマンドが
    使用不可
  end note

この図が示すように、unlinklink を繰り返すことで、必要に応じてバージョンを自由に切り替えられます。

エラー対応例

実際の作業では、以下のようなエラーに遭遇することがあります。

Error: No available formula with the name "node@14.17.0"

エラーコード: Formula not found

エラーメッセージ:

bashError: No available formula with the name "node@14.17.0".
Did you mean node@14, node@16 or node@18?

発生条件: brew extract を実行せずに直接インストールしようとした場合

解決方法:

  1. まず brew extract で Formula を抽出する
  2. その後に brew install を実行する
bash# 正しい手順
brew extract --version=14.17.0 node homebrew/local
brew install node@14.17.0

Error: Invalid version: 14.17.0

エラーコード: Invalid version specification

エラーメッセージ:

bashError: Invalid version: 14.17.0
The specified version does not exist in the repository history.

発生条件: 存在しないバージョンを指定した場合

解決方法:

  1. Node.js 公式サイトで正しいバージョン番号を確認
  2. GitHub の Homebrew/homebrew-core で該当バージョンの Formula が存在するか確認
  3. 正しいバージョン番号で再実行

Error: Permission denied

エラーコード: EACCES (Permission denied)

エラーメッセージ:

bashError: Permission denied @ apply2files - /opt/homebrew/bin/node

発生条件: リンク先のディレクトリに書き込み権限がない場合

解決方法:

  1. Homebrew ディレクトリの所有者を確認・修正する
bash# 所有者確認
ls -la /opt/homebrew/bin/node
bash# 所有者を現在のユーザーに変更
sudo chown -R $(whoami) /opt/homebrew
bash# 再度リンクを試行
brew link node@14.17.0 --overwrite

まとめ

Homebrew での複数バージョン共存は、brew extractbrew link の組み合わせで実現できます。

この記事では、以下の内容を解説しました。

  • Homebrew の基本的なバージョン管理の仕組みと、複数バージョン共存が必要な理由
  • 従来の方法の課題と、brew extract がどのように解決するか
  • ローカル Tap の作成から Formula の抽出、インストールまでの手順
  • brew link と brew unlink を使った実際のバージョン切り替え方法
  • よくあるエラーと解決策を含む実践的なトラブルシューティング

この方法を使えば、プロジェクトごとに異なるバージョンの Node.js や Python、その他のツールを柔軟に切り替えられるようになります。

ただし、頻繁にバージョンを切り替える場合は、nodenvpyenv のような専用のバージョン管理ツールの導入も検討してみてください。これらのツールは、プロジェクトごとに自動的にバージョンを切り替える機能を提供しています。

図で理解できる要点:

  • Homebrew は Cellar に複数バージョンを格納できるが、リンクは 1 つだけ
  • brew extract で過去の Formula を取り出し、ローカル Tap に保存する
  • brew linkbrew unlink の繰り返しでバージョンを切り替える

関連リンク