SNSピリカやタカノメのサービスでは、バックエンドで度々Pythonを使用しています。その中でいくつかPythonで書かれたコードがあり、プロジェクトに応じ別のPythonバージョンで実装しています。
しかしながら、Mac(M1 CPU)からarm64ベースのアーキテクチャになったこともあり、一部Pythonバージョンや一部ライブラリがインストールできないことが多々あります。
そこで、複数のPythonバージョンで仮想環境を切り分けられるよう、pyenv + pipenvの環境を整えました。
(2024.01 追記)
もしWebフロントエンドなど、複数言語のバージョン管理が必要であれば、pyenvのかわりにmise(旧rtx)(ないし分岐元のasdf)やanyenvを利用しても良いかもしれません。特にasdf(ないしrtx)は様々な言語をプラグインでサポートしています(参考)。
(2022.03.21 追記)
MacOS Monterey(12.3)からApple Clangが13.1.6になり、過去のPythonバージョンに適合しないようになっています。そのためpyenvにおいて、x86_64・arm64環境の両方で以下バージョンがインストールできません(後述のエラーが出ます)。
- 3.8系: 3.8.12以下
- 3.9系: 3.9.7以下
- 3.10系: 3.10.0
- (3.7系: 3.7.12以下)
その場合、より新しいバージョンをインストールするか、旧バージョンにパッチが出るまでお待ち下さい(参考ページ: pyenv issue#2143)。
ビルド時のエラー内容
configure: error: internal configure error for the platform triplet, please file a bug report
モチベーション
- Pythonを使う限りは、なるべくarm64環境で動かした方がパフォーマンスが良い傾向にあります
- 互換性のため、Python 3.8未満の古いバージョンも使えることが必要です
- 追記: 2023/6/27に3.7系のサポートが終了しましたため、早期に3.8以上(できれば3.9以上へのアップデートが推奨されます)。
前提知識
- Python 3.8未満は、公式にM1(arm64)をサポートしていません (python bug tracker メッセージ382939より)。セキュリティアップデートのみが提供されるバージョンのため、今後もサポートされません1
- x86_64環境でも、pyenvで3.7.8未満をビルドするとインストールに失敗します。当該バージョンを入れたい場合、後述の対策を入れる必要があります
- pyenv(および各種Python), pipenvともにHomebrewをインストールしたアーキテクチャに合わせる必要があります(合わせない場合、Pythonのビルドや依存ライブラリのインストールに失敗します)
- pyprojなど、arm64環境でインストールできないライブラリがあります
基本方針
- Python 3.8未満、もしくはarm環境で動かないライブラリを含む場合: x86_64(Rosetta 2)環境で動かします
- Python 3.8以上: arm64環境で動かします
構築した環境例
検証環境
- MacBook Pro (M1, 2020)
- macOS Monterey バージョン12.1
ディレクトリ構成
本稿では、以下の構成で環境構築を行うものとします。
- arm64環境で、Python 3.9.7, 3.9.9, 3.10.0を使用
- x86_64環境で、Python 3.7.6, 3.7.12, 3.9.7を使用
これにより、インストールされるHomebrew、pyenv、pipenvおよび仮想環境の関係は以下の通りとなります。
arm64環境
/opt/homebrew/bin: arm64向けHomebrew
- /opt/homebrew/bin/pyenv: arm64向けpyenvバイナリ
~/.pyenv_arm64/: バージョンごとのPython置き場 (>= 3.8.0)
- ~/.pyenv_arm64/versions/3.9.9/: Python 3.9.9
- ~/.pyenv_arm64/versions/3.9.9/bin/pipenv: Python 3.9.9環境下のPipenv
- ~/.pyenv_arm64/shims/pipenv: pipenvのエントリポイント(現在選択しているPythonバージョンに合わせて、pipenvを選択する)
x86_64環境
/usr/local/bin: x86_64向けHomebrew
- /usr/local/bin/pyenv: x86_64向けpyenvバイナリ
~/.pyenv_x86/: バージョンごとのPython置き場(< 3.8.0)
- ~/.pyenv_x86/versions/3.7.6/: Python 3.7.6
- ~/.pyenv_x86/versions/3.7.6/bin/pipenv: Python 3.7.6環境下のPipenv
- ~/.pyenv_x86/versions/3.7.12/: Python 3.7.12
- ~/.pyenv_x86/versions/3.7.12/bin/pipenv: Python 3.7.12環境下のPipenv
- ~/.pyenv_x86/shims/pipenv: pipenvのエントリポイント(現在選択しているPythonバージョンに合わせて、pipenvを選択する)
構築フロー
以下のフローに従って環境構築をすすめます2。
- (任意) arm64, x86_64環境をコマンドで切り替えられるようにする
- x86_64版、arm64版両方でHomebrewをインストール、環境構築する
- x86_64版、arm64版両方でpyenvをインストール、環境構築する
- 各pyenvの各Pythonバージョンをインストール
- 各pyenvの各Pythonバージョンでpipenvをインストール※
※ Homebrewを使って、$ brew install pipenv
でグローバルにpipenvをインストールすることもできます。こちらであれば、1回pipenvをインストールすれば5.のステップが不要になります。本紙では、pyenvで切り分けたバージョン毎に環境を独立させたいため、個別にpipenvを入れるようにしています。
1. (任意) arm64, x86_64環境をコマンドで切り替えられるようにする
以下、~/.zshrcに追記します。これにより、x86
を実行するとx86_64環境、arm
を実行するとarm64環境でシェルが動作するようになります。
alias x86='arch -x86_64 zsh'
alias arm='arch -arm64e zsh'
2. x86_64版、arm64版両方でHomebrewをインストール、環境構築
arm64版を以下のスクリプトでインストールします。
$ uname -m # arm64環境で動いていることを確認する
arm64
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arm64版をインストールできていれば、which brew
でopt/homebrew/bin/brewがHomebrewの実行パスとして表示されます。
$ which brew
opt/homebrew/bin/brew
x86_64版を以下のスクリプトでインストールします。
$ x86
$ uname -m # x86_64環境で動かせていることを確認する
x86_64
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
x86_64版をインストールできていれば、which brew
で/usr/local/bin/brewがHomebrewの実行パスとして表示されます。
$ which brew
/usr/local/bin/brew
最後に、~/.zshrcで以下を追記し、brewのパスがPATHに入るようにします。
if [ "$(uname -m)" = "arm64" ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
export PATH="/opt/homebrew/bin:$PATH"
else
eval "$(/usr/local/bin/brew shellenv)"
fi
※x86
やarm
を実行したときにPATHがアップデートされるよう、~/.zshrcに記載しています。ログインシェルにのみ適用させるのであれば~/.zprofileに追記すべきですが、そうするとx86
やarm
実行後にbrewの実行パスが切り替わりません。また、x86
,arm
実行の度に上記pathがどんどん追記されますが、実用上問題ありません。
この時点で、下図の通りx86_64版, arm64版のHomebrew両方が実行可能になります。
3. x86_64版、arm64版両方でpyenvをインストール、環境構築
arm版のHomebrewでpyenvをインストールします。
$ uname -m # もし結果がx86_64であれば、`arm`を実行後に再度実行する
arm64
$ brew install pyenv
x86_64版のHomebrewでpyenvをインストールします。
$ uname -m # もし結果がarm64であれば、`x86`を実行後に再度実行する
x86_64
$ brew install pyenv
最後に、~/.zshrcで以下を追記し、pyenvの初期化と実行パス追加が実行されるようにします。arm64版のpyenvは~/.pyenv_arm64、x86_64版のpyenvは~/.pyenv_x86下に各Pythonバージョンがインストールされるようにします。
なお、eval "$(pyenv init -)"
では実行パス追加が行われないので、eval $(pyenv init --path)
も合わせて実行する必要があります3。
if [ "$(uname -m)" = "arm64" ]; then
export PYENV_ROOT="$HOME/.pyenv_arm64"
export PATH="$HOME/.pyenv_arm64/bin:$PATH"
else
export PYENV_ROOT="$HOME/.pyenv_x86"
export PATH="$HOME/.pyenv_x86/bin:$PATH"
fi
eval "$(pyenv init -)"
eval "$(pyenv init --path)"
4. 各pyenvの各Pythonバージョンをインストール
Python 3.8以上はarm64版のpyenvでインストールします。また、Python 3.7以下もしくはx86_64環境のPythonが必要な場合はx86_64版のpyenvでインストールします。
Python 3.9.9をインストールする場合
$ arm
$ where pyenv # pyenv () {} の次に、/opt/homebrew/bin/pyenvが入っているはず
pyenv () {
...
}
/opt/homebrew/bin/pyenv
...
$ pyenv install 3.9.9
Python 3.7.12をインストールする場合
$ x86
$ where pyenv # pyenv () {} の次に、/usr/local/bin/pyenvが入っているはず
pyenv () {
...
}
/usr/local/bin/pyenv
...
$ pyenv install 3.7.12
上記のプロセスに従って、残りのPythonバージョンについてもインストールしておきます。
なお、Python 3.7.8未満もしくは3.8.4未満バージョンはそのままではインストールできません。これらよりも新しいバージョンをインストールするか、インストール時にPythonビルドに必要な依存ライブラリのパス指定やパッチ適用を行う必要があります。
4.1. Python 3.7.6
x86_64環境でも、単純にpyenv install 3.7.6
とするとビルドに失敗します。
BUILD FAILED
...
./Modules/posixmodule.c:9197:15: error: implicit declaration of function 'sendfile' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
ret = sendfile(in, out, offset, &sbytes, &sf, flags);
^
1 error generated.
make: *** [Modules/posixmodule.o] Error 1
make: *** Waiting for unfinished jobs....
1 warning generated.
以下の通りパラメータを加えた上でPython 3.7.6をビルドするとインストールできます。
- Big Sur以降向けのパッチを当てる
- pythonビルド時にopenssl, bzip2, readline, zlibのライブラリパス, インクルードパスを指定
CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib" pyenv install --patch 3.7.6 < <(curl -sSL https://github.com/python/cpython/commit/8ea6353.patch\?full_index\=1)
Python 3.8.3の場合
こちらも同様に、posixmodule.c周りのビルドでエラーが発生します。パッチおよびインクルードパス等を指定してビルドしてください。
BUILD FAILED
...
./Modules/posixmodule.c:9197:15: error: implicit declaration of function 'sendfile' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
ret = sendfile(in, out, offset, &sbytes, &sf, flags);
^
1 error generated.
make: *** [Modules/posixmodule.o] Error 1
make: *** Waiting for unfinished jobs....
1 warning generated.
CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib" pyenv install --patch 3.8.3 < <(curl -sSL https://github.com/python/cpython/commit/8ea6353.patch\?full_index\=1)
ひとしきりPythonバージョンのインストールができると、下図の状態になります。
5. 各pyenvの各Pythonバージョンでpipenvをインストール
pyenvで適宜Pythonバージョンを切り替えつつ、各Pythonバージョンごとにpipenvをインストールします。pipenvはpythonによる1ライブラリなので、Pythonバージョンごとにpip installする必要があります。
Python 3.7.12での例
$ x86
$ pyenv global 3.7.12
$ pyenv versions # 今、どのpythonバージョンを使っているか確認
system
* 3.7.12 (set by /Users/ユーザ名/.pyenv_x86/version)
3.7.6
$ pip install pipenv # python 3.7.12環境にpipenvがインストールされる
...
$ pipenv --version
pipenv, version 2021.11.23
Python 3.9.9での例
$ arm
$ pyenv global 3.9.9
$ pyenv versions # 今、どのpythonバージョンを使っているか確認
system
* 3.9.9 (set by /Users/ユーザ名/.pyenv_arm64/version)
$ pip install pipenv # python 3.9.9環境にpipenvがインストールされる
...
$ pipenv --version
pipenv, version 2021.11.23
なお、pyenvで選択したPython環境にpipenvが入っていない場合は以下のエラーとなります。
$ arm
$ pyenv global 3.10.1
$ pipenv --version
pyenv: version `3.10.1' is not installed (set by PYENV_VERSION environment variable)
あとは同様にして、各Pythonバージョンごとにpipenvをインストールします。完了すると、下図の状態になります。
6. Pythonによるプロジェクトごとに仮想環境を作る
使いたいPythonのバージョンを指定しつつ、仮想環境を使用します。下記の様に、使用したいPythonを明示的に指定する必要があります。pipenvでは複数マイナーバージョンの環境があるとき、最新のものを使おうとするためです(pipenv global
やpipenv shell
で明示的にマイナーバージョンを指定しても同様)。
pipenv install --python 3.9.7
なお、.python-versionが存在していれば、下記の様に指定することもできます。
pipenv install --python $(cat .python-version)
以上になります、ご覧いただきありがとうございます!
-
Python 3.8もセキュリティアップデートのみ提供される段階に入っているものの、arm64環境で動作するようサポートされています ↩
-
M1MacのRosettaとARM環境にpyenv + pipenvの環境構築を行うによる環境構築を参考にしました ↩
-
pyenv init -
のみを実行してみると、"WARNING: `pyenv init -` no longer sets PATH."と警告されるようになっています ↩