こんにちは、ピリカ開発チームの九鬼です。
SNSピリカやタカノメのサービスでは、バックエンドで度々Pythonを使用しています。その中でいくつかPythonスクリプトがあり、バージョンが3.7系列や3.9系列などが含まれます。
しかしながら、Mac(M1 CPU)からarm64ベースのアーキテクチャになったこともあり、一部Pythonバージョンや一部ライブラリがインストールできないことが多々あります。
そこで、複数のPythonバージョンで仮想環境を切り分けられるよう、pyenv + pipenvの環境を整えました。
(2022.03.21 追記) MacOS Monterey(12.3)からApple Clangが13.1.6になり、過去のPythonバージョンに適合しないようになっています。そのためpyenvにおいて、x86_64・arm64環境の両方で以下バージョンがインストールできません(後述のエラーが出ます)。
- 3.7系: 3.7.12以下
- 3.8系: 3.8.12以下
- 3.9系: 3.9.7以下
- 3.10系: 3.10.0
その場合、より新しいバージョンをインストールするか、旧バージョンにパッチが出るまでお待ち下さい(参考ページ: pyenv issue#2143)。
ビルド時のエラー内容
configure: error: internal configure error for the platform triplet, please file a bug report
モチベーション
前提知識
- Python 3.8未満はm1(arm64)サポートしていません (python bug tracker メッセージ382939より)。セキュリティアップデートのみが提供されるバージョンのため、今後もサポートされません1
- x86_64環境でも、pyenvで3.7.8未満をビルドするとインストールに失敗します。当該バージョンを入れたい場合、後述の対策を入れる必要があります
- pyenv(および各種Python), pipenvともにHomebrewをインストールしたアーキテクチャに合わせる必要があります(合わせない場合、Pythonのビルドや依存ライブラリのインストールに失敗します)
- pyprojなど、arm64環境でインストールできないライブラリがあります
基本方針
構築した環境例
検証環境
- MacBook Pro (M1, 2020)
- macOS Monterey バージョン12.1
ディレクトリ構成
本稿では、以下の構成で環境構築を行うものとします。
これにより、インストールされる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.12/: Python 3.7.12
- ~/.pyenv_x86/shims/pipenv: pipenvのエントリポイント(現在選択しているPythonバージョンに合わせて、pipenvを選択する)
構築フロー
以下のフローに従って環境構築をすすめます2。 1. (任意) arm64, x86_64環境をコマンドで切り替えられるようにする 2. x86_64版、arm64版両方でHomebrewをインストール、環境構築する 3. x86_64版、arm64版両方でpyenvをインストール、環境構築する 4. 各pyenvの各Pythonバージョンをインストール 5. 各pyenvの各Pythonバージョンで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 $ 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.“と警告されるようになっています↩