macOS+pyenvでpypy3.6-7.3.1をインストールできなかったがpypy3.6-7.3.0はインストールできた。モンテカルロ法でpypyの風を感じた。

# まとめ

- pyenv で pypy をインストールしようとすると 403エラーが返ってきた
    - pyenv の更新により、この問題を解決できた
- しかし、pypy3.6-7.3.1 はインストールできなかった (未解決issueに行き当たった)
- 他方で、pypy3.6-7.3.0 をインストールすることはできた
- おまけ: pypy で円周率を計算させたところ、CPython と比較して約4.7倍速く動作した (簡易的な実験のみ実施)

# 第一の現象: pyenv で pypy をインストールしようとすると 403 エラーが返ってくる

pyenv で pypy3.6-7.3.1 をインストールしようとしたところ、エラーメッセージが表示され、インストールに失敗した。

```text
% pyenv install -v pypy3.6-7.3.1
/var/folders/fv/495qgk712lg73r5dxmr83mhh0000gn/T/python-build.20200822165404.41441 ~/work
Downloading pypy3.6-v7.3.1-osx64.tar.bz2...
-> https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.1-osx64.tar.bz2
curl: (22) The requested URL returned error: 403
error: failed to download pypy3.6-v7.3.1-osx64.tar.bz2

BUILD FAILED (OS X 10.15.6 using 0000000000)
```

## 前提環境

- macOS Catalina 10.15.6
- pyenv 1.2.20

## 原因

pyenvが参照するmacOS用のアーカイブファイルのURLから、403エラーが返ってくること。

```text
Downloading pypy3.6-v7.3.1-osx64.tar.bz2...
-> https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.1-osx64.tar.bz2
curl: (22) The requested URL returned error: 403
```

これは、[BitbucketがMercurialのサポートをやめ](https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket)、2020年7月8日以降、リポジトリの中身にアクセスできなくなったから。

## 解決策

pyenvの最新ソースコードを利用すれば良い。なぜなら、この記事執筆時点の2日前 (2020/08/20) に、問題が修正されているから。以下のプルリクエストを参照:

- [Fix PyPy download links #1682](https://github.com/pyenv/pyenv/pull/1682)

### brewでpyenvをインストールしている場合

- pyenvの更新版リリースを待つ
- もしくは、pyenvをGitHubから直接インストールする

ここでは、brewでインストールしたpyenvを消去し、GitHubからの直接インストール方式に変更する手順を説明する。

#### pyenvインストール方法の移行: brewからGitHubへ

まず、pyenv の root ディレクトリが `~/.pyenv` にあることを確認しておく。(以下の手順は、このパスに root ディレクトリが存在することを前提に記載している。)

```zsh
% pyenv root
/Users/snjot/.pyenv
```

brew uninstall で pyenv をシステムから削除する。

```zsh
% brew uninstall pyenv
Uninstalling /usr/local/Cellar/pyenv/1.2.20... (708 files, 2.5MB)
```

あとは、[Basic GitHub Checkout](https://github.com/pyenv/pyenv#basic-github-checkout) 記載の手順を実施すれば良い。

ただし、brew版pyenvが作成した `~/.pyenv` ディレクトリが残っているため、そのままだと手順通りに git clone できない。そこで、手順 "1. Check out pyenv where you want it installed." に一工夫加える。以下のようにすると良い:

- その1. `.git` ディレクトリの移植

```zsh
% git clone https://github.com/pyenv/pyenv.git ~/.pyenv-temp
Cloning into '/Users/snjot/.pyenv-temp'...
remote: Enumerating objects: 123, done.
remote: Counting objects: 100% (123/123), done.
remote: Compressing objects: 100% (121/121), done.
remote: Total 18256 (delta 106), reused 5 (delta 0), pack-reused 18133
Receiving objects: 100% (18256/18256), 3.65 MiB | 2.80 MiB/s, done.
Resolving deltas: 100% (12440/12440), done.
```

```zsh
% mv ~/.pyenv-temp/.git ~/.pyenv/.git
% rm -rf ~/.pyenv-temp
```

- その2. `.git` からのファイル復元

```zsh
% cd ~/.pyenv
```

```zsh
% git reset HEAD --hard
HEAD is now at dc4e24e6 Fix PyPy download links (#1682)
```

残りは[公式手順](https://github.com/pyenv/pyenv#basic-github-checkout)を参照。

最後に、正しくインストールできているか確認しておくと安心です。

```zsh
% pyenv --version
pyenv 1.2.20-2-gdc4e24e6
```

### GitHubから直接インストールしている場合

pyenvのバージョンを更新し、[dc4e24e](https://github.com/pyenv/pyenv/commit/dc4e24e681f5ac8235ab6e2f2e20b516e6997a01) 以降のものを使う。

[公式手順](https://github.com/pyenv/pyenv#upgrading)に沿って更新すれば良い。参考までに、以下にコマンドを転記しておく:

```zsh
cd $(pyenv root)
git pull
```

# 第二の現象: 403エラー解決後もビルドエラーが出て pypy3.6-7.3.1 をインストールできない

解決策実施後に、pypy3.6-7.3.1 をインストールしようとして、以下のエラーに遭遇した:

```
% pyenv install pypy3.6-7.3.1
Downloading pypy3.6-v7.3.1-osx64.tar.bz2...
-> https://downloads.python.org/pypy/pypy3.6-v7.3.1-osx64.tar.bz2
Installing pypy3.6-v7.3.1-osx64...
/Users/snjot/.pyenv/plugins/python-build/bin/python-build: line 1590: 44564 Abort trap: 6           "$PYTHON_BIN" -c "import $1" > /dev/null 2>&1
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
/Users/snjot/.pyenv/plugins/python-build/bin/python-build: line 1590: 44565 Abort trap: 6           "$PYTHON_BIN" -c "import $1" > /dev/null 2>&1
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
/Users/snjot/.pyenv/plugins/python-build/bin/python-build: line 1599: 44566 Abort trap: 6           "$PYTHON_BIN" -c "import $1" > /dev/null 2>&1
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

Please consult to the Wiki page to fix the problem.
https://github.com/pyenv/pyenv/wiki/Common-build-problems
```

調査したところ、未解決issueに行き着いたため、pypy3.6-7.3.1 のインストールを断念した。

なお、ひとつ手前のバージョン pypy3.6-7.3.0 のインストールには、成功した。

```zsh
% pyenv install pypy3.6-7.3.0
Downloading pypy3.6-v7.3.0-osx64.tar.bz2...
-> https://downloads.python.org/pypy/pypy3.6-v7.3.0-osx64.tar.bz2
Installing pypy3.6-v7.3.0-osx64...
Installed pypy3.6-v7.3.0-osx64 to /Users/snjot/.pyenv/versions/pypy3.6-7.3.0
```

## 調査内容

まずは、画面に出力された指示に従い、以下のURLを確認した:

> Please consult to the Wiki page to fix the problem.
> https://github.com/pyenv/pyenv/wiki/Common-build-problems

そして、Prerequisitesを満たしているか確認した。筆者の場合、Prerequisitesを満たしていた。

次に、[上記 URL](https://github.com/pyenv/pyenv/wiki/Common-build-problems) に記載されている解決策を一通り試した。しかし、問題は解決しなかった。

pypy3.6-7.3.1 に特殊な問題なのかどうか調べるため、以下コマンドで pypy3.6-7.3.0 をインストールしたところ、問題なくインストールできた。

```zsh
% pyenv install pypy3.6-7.3.0
Downloading pypy3.6-v7.3.0-osx64.tar.bz2...
-> https://downloads.python.org/pypy/pypy3.6-v7.3.0-osx64.tar.bz2
Installing pypy3.6-v7.3.0-osx64...
Installed pypy3.6-v7.3.0-osx64 to /Users/snjot/.pyenv/versions/pypy3.6-7.3.0
```

pyenv の GitHub リポジトリを確認し、issues を検索したところ、同様の問題が見つかった:

- [Missing the OpenSSL lib? on installing pypy3.6-v7.3.1 by pyenv 1.2.19 #1639](https://github.com/pyenv/pyenv/issues/1639)

記事執筆時点では未解決 issue であるため、pypy3.6-v7.3.1 のインストールを断念した。

# Hello, pypy!

pypy3.6-7.3.0 を導入できたので、動かしてみた。

```text
% pyenv shell pypy3.6-7.3.0
% python
Python 3.6.9 (1608da62bfc7, Dec 23 2019, 10:50:17)
[PyPy 7.3.0 with GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>>
```

ちゃんと動いている :thumbsup:

# おまけ: 円周率を計算させてpypyの風を感じる

せっかくなので、モンテカルロ法による円周率の近似計算で、pypyの速さを感じてみました。pypy の方が 4.7 倍ほど速く動きました。すばらしいですね。

## コード

```python
import math
import random
import time
from typing import List, Tuple


def generate_random_point2d() -> Tuple[float, float]:
    return (random.random(), random.random())


def generate_multiple_random_points2d(num_samples: int) -> List[Tuple[float, float]]:
    points = []
    for _ in range(num_samples):
        points.append(generate_random_point2d())
    return points


def calculate_l2norm(point2d: Tuple[float, float]) -> float:
    return math.sqrt(point2d[0] ** 2 + point2d[1] ** 2)


def count_points_within_quadrant(l2norms: List[float]) -> int:
    num_points = 0
    for l in l2norms:
        if l <= 1:
            num_points += 1
    return num_points


def calculate_pi(num_samples: int) -> float:
    points = generate_multiple_random_points2d(num_samples)
    l2norms = [calculate_l2norm(p) for p in points]
    num_points_within_quadrant = count_points_within_quadrant(l2norms)
    pi = 4 * num_points_within_quadrant / num_samples
    return pi


if __name__ == "__main__":
    random.seed(0, version = 2)

    tic = time.time()
    pi = calculate_pi(10_000_000)
    tac = time.time()

    print(f"pi = {pi}; ({tac - tic} sec)")
```

[こちらのGitHubリポジトリ](https://github.com/snjot/pimonte)にコードを公開しています。

## 簡易実験

まず、pypyで実行:

```zsh
% python -m pimonte
pi = 3.1413028; (1.8637058734893799 sec)
```

次に、CPythonで同じコードを実行:

```zsh
% python -m pimonte
pi = 3.1413028; (8.742990016937256 sec)
```

計算に要した時間を比較すると、pypy が CPython の 4.69 倍速く計算できていました。

簡易的な実験なので、考察は省きます。

みなさんも、pypy の速さを感じてみてください。以上！