🙌

M1 MacでのPython/Node.js環境構築:ネイティブ拡張モジュールのビルドエラー徹底解説と対処法

に公開

はじめに

M1チップ搭載Mac (Apple Silicon) で開発環境を構築する際、PythonやNode.jsの特定のパッケージでビルドエラーが発生する事象が確認されている。本稿は、この問題に関する技術的な備忘録である。

  • 環境:

    • マシン: MacBook Pro (14-inch, M1 Pro)
    • OS: macOS Sequoia 15.6
    • Python: 3.11.8 (pyenv 2.4.0経由でインストール)
    • Node.js: v20.12.2 (nvm 0.39.7経由でインストール)
  • 発生事象:

    • pip install tensorflownpm install node-sass などを実行した際、C/C++ソースコードのコンパイル段階で失敗し、アーキテクチャに関連するエラーメッセージが出力される。

原因

M1チップのアーキテクチャはarm64 (aarch64) である。一方、従来のIntel Macはx86_64アーキテクチャを採用していた。このアーキテクチャの差異がビルドエラーの主要因となる。

  1. arm64アーキテクチャ非対応のパッケージ:
    パッケージがx86_64アーキテクチャにのみ対応しており、arm64向けのソースコード修正やビルド設定が提供されていない場合、ネイティブでのコンパイルに失敗する。arm64向けのプリコンパイル済みバイナリが提供されていないため、ローカルでのビルドが試みられ、結果としてエラーとなる。

  2. 依存ライブラリのパスの問題:
    M1ネイティブのHomebrewは、ライブラリを/opt/homebrewディレクトリにインストールする。これはIntel Macでの/usr/localとは異なる。ビルドスクリプトが従来のパスをハードコードしている場合、必要なヘッダファイルやライブラリを見つけられずにビルドが失敗することがある。

  3. ビルドツールのバージョン互換性:
    古いバージョンのpipnode-gypなどが、arm64アーキテクチャを正しく認識・処理できない場合がある。

解決策

問題の性質に応じて、複数の解決策が存在する。推奨度順に記述する。

解決策1: arm64ネイティブ対応版または代替パッケージの利用 (推奨)

最もクリーンでパフォーマンス上の利点も大きい解決策は、arm64にネイティブ対応したパッケージや、アーキテクチャに依存しない代替パッケージを利用することである。

Pythonの例: tensorflow

標準のtensorflowパッケージは、M1 Macへの対応が遅れていた。Appleが提供する専用パッケージtensorflow-macosを利用する。

# 既存のtensorflowをアンインストール
pip uninstall tensorflow

# Appleが提供するパッケージをインストール
pip install tensorflow-macos

補足: 現在ではtensorflowパッケージ自体がarm64対応のバイナリを提供するようになっているため、最新版ではこの手順は不要な場合がある。詳細はTensorFlow公式ドキュメントを参照のこと。

Node.jsの例: node-sass

node-sassはC++で書かれたlibsassに依存しており、M1 Macでのビルドエラーが頻発する代表的なパッケージである。libsass自体が非推奨となったため、Dartで実装されたsassパッケージへの移行が公式に推奨されている。

# node-sassをアンインストール
npm uninstall node-sass

# sassをインストール
npm install sass

sassはJavaScriptにコンパイルされるため、ネイティブ拡張のビルドが不要となり、アーキテクチャの問題を回避できる。

解決策2: Rosetta 2環境での実行

ネイティブ対応版が存在しない、または移行が困難な場合に有効な次善策である。Rosetta 2は、x86_64アーキテクチャ向けのアプリケーションやコマンドをarm64上で実行するためのトランスレーションレイヤーである。

ターミナルセッションをx86_64で実行する

恒久的な設定変更は不要で、現在のターミナルセッションのみをx86_64環境で実行する方法が最も管理しやすい。

# 現在のアーキテクチャを確認
arch
# => arm64

# x86_64アーキテクチャで新しいシェルを起動
arch -x86_64 zsh

# 新しいシェルでのアーキテクチャを確認
arch
# => i386 (x86_64を示す)

このx86_64シェル内でnpm installpip installを実行することで、パッケージはx86_64アーキテクチャとしてインストール・ビルドされる。

x86_64版Homebrewと開発ツールの導入

Rosetta 2環境でNode.jsやPythonそのものを管理することも可能である。

  1. x86_64版Homebrewのインストール:
    Intel版のHomebrewは/usr/localにインストールされる。

    arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
  2. x86_64版Node.js/Pythonのインストール:
    x86_64版のbrewコマンドを使ってインストールする。

    # x86_64版のnvm/nodeをインストール
    arch -x86_64 brew install nvm
    # .zshrcなどにnvmのセットアップを記述後、x86_64シェルでnodeをインストール
    # arch -x86_64 zsh
    # nvm install 16
    
    # x86_64版のpyenv/pythonをインストール
    arch -x86_64 brew install pyenv
    # x86_64シェルでpythonをインストール
    # arch -x86_64 zsh
    # pyenv install 3.9.13
    

この方法では環境が二重管理となり複雑化するため、プロジェクト単位での利用に留めるのが賢明である。

解決策3: ビルド環境変数を手動で設定

ビルドエラーのログを解析し、不足しているライブラリへのパスを環境変数で明示的に指定する方法。これは特定ライブラリのビルド失敗に限定的ながら有効である。

例えば、cryptographyパッケージのビルド時にopensslが見つからない場合、Homebrewでインストールしたopensslのパスを指定する。

# Homebrewでopensslをインストール (arm64ネイティブ)
brew install openssl

# 環境変数を設定してpip installを実行
LDFLAGS="-L/opt/homebrew/opt/openssl/lib" \
CPPFLAGS="-I/opt/homebrew/opt/openssl/include" \
pip install cryptography

この方法は根本的な解決ではなく、パッケージごとに設定が必要となるため、最終手段と位置づけるべきである。

まとめ

M1 Macにおけるネイティブ拡張モジュールのビルドエラーは、主にarm64アーキテクチャへの移行期に起因する問題である。

  • 第一選択: arm64ネイティブ対応パッケージ、またはアーキテクチャ非依存の代替パッケージへ移行する。
  • 第二選択: プロジェクト要件で古いパッケージが必須の場合、Rosetta 2を利用してx86_64環境でビルドする。
  • 最終手段: エラーログを解析し、環境変数で依存ライブラリのパスを個別に指定する。

開発環境のバージョン管理ツール(pyenv, nvmなど)を利用することで、arm64版とRosetta 2 (x86_64) 版の環境を切り替えて管理することが可能となり、プロジェクト間の影響を最小限に抑えることができる。

参照

Discussion