[Python3.7] windows10上の Jupyter Notebook で Cython を動かそうとしたら "DistutilsPlatformError: Unable to find vcvarsall.bat"

# Summary

windows10にpythonをインストールし、Jupyter NotebookでCythonの力を試そうとしたところ、`DistutilsPlatformError: Unable to find vcvarsall.bat`というエラーに遭遇。

- 初回: 対処に4時間かかったorz
- 4年後: 対処に1.5日かかった..._(⌒(\_´-ω-)\_

でも！対処できたので記録を残しておこうと思います。

多分、この記事が参考になるという人はほとんどいないのではないかと思います....（危険な方法のため。もし、他の方法があったら是非教えて下さい）

# 環境1

4年前にこの問題に出くわした時の環境。

|  - | version |
|:--:|:------|
|windows|10|
|Visual Studio|Express Edition 2017|
|Anaconda3|2019.07 (Python3.7.3 64-bit)|
|python|3.7.3|
|Cython|0.29.12|

# 環境2

2023/10/01 に再び出くわした時の環境。

| - |version|
|:--:|:------|
|windows|10|
|Visual Studio|Express Edition 2017|
|pyenv-win|3.1.1|
|python|~~3.8.10~~ -> 3.7.3|
|Cython|3.0.2|
|swig|4.0.1|

-> python 3.8.10 では解決できず、 3.7.3 に下げて4年前と同じ方法で無理やり解決させた。


# 症状

## Cython 実行時エラー

```python:jupyter-notebook
# 参考にした記事: https://qiita.com/pashango2/items/45cb85390193d97523ca
%load_ext Cython

# --- next cell ---
%%cython
def py_fib(n):
    a, b = 0.0, 1.0
    for i in range(n):
        a, b = a + b, a
    return a

# result
...(略)
-> DistutilsPlatformError: Unable to find vcvarsall.bat
```

## swig 実行時エラー

```powershell
> python setup.py build_ext --inplace
running build_ext
building '_pafprocess' extension
swigging pafprocess.i to pafprocess_wrap.cpp
C:\swigwin-4.0.1\swig.exe -python -c++ -o pafprocess_wrap.cpp pafprocess.i
error: Unable to find vcvarsall.bat
```


# 対処
1. pythonをコンパイルしたVC++のバージョンを調査
1. Visual Studioをインストール  
もちろん、1. で調べたバージョンをインストールする
1. Cythonのソースを無理やり書き換えて、`vcvarsall.bat`を見つけさせた (swig 実行時エラー発生時は不要っぽい？)

他の方法も色々試したんですが...この方法しか成功しませんでした

「なんでこんな方法にしたの？」と思われた方は、[ここ](#参考にした記事)をご覧くださいな(;_;)

## 1. pythonをコンパイルしたVC++のバージョンを調査
```bash:cmd
C:\Users\PCUser>python
>>> import sys
>>> sys.version
'3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)]'
```
ということで、コンパイラ VC++ のバージョンは V14.15 (Visual C++ 2017) と判明

Visual Studio のバージョン を `sys.version` の出力結果からたどる場合、 wikipedia にも情報が書かれているが、

https://ja.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B

次のサイトの方が詳しい。

https://qiita.com/yumetodo/items/8c112fca0a8e6b47072d

## 2. Visual Studio インストール

### 2017 ※以下の方法は、今は使えないようです

1. ~~次のURLを開く~~  
~~https://visualstudio.microsoft.com/ja/vs/express/~~
1. ~~下にスクロールして以下の赤枠位置をクリックしてインストーラーをダウンロード~~
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/965bf2b6-133a-c171-02b3-a7fec219f2de.png)
1. ~~ダウンロードされたファイルを実行~~  
`vs_wdexpress.exe`  
~~特に設定変更などはせず~~
1. `vcvarsall.bat`ファイルを探す！  
私の場合、ここにあった...↓  
`C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\VC\Auxiliary\Build`

### 2017 ※代替策 (2023/10/01)

こちらの記事で、 Visual Studio express 2017 のインストール方法が書いてありました。

https://qiita.com/toras9000/items/42b5a2c093a0dba1d8b7

以下のサイトなどから、VIsual Studio 2022 などのインストーラを入手し、

https://visualstudio.microsoft.com/ja/vs/express/

powershell で、そのファイルがあるフォルダまで移動してから、以下のコマンドを実行します。

```powershell:powershell
# VisualStudioSetup.exe の部分は、ダウンロードしたファイル名で置き換えてね
.\VisualStudioSetup.exe --channelUri https://aka.ms/vs/15/release/channel --productId Microsoft.VisualStudio.Product.WDExpress
```

すると、Visual Studio 2017 のインストールが開始できる状態になります。
install し終わったら、念のため windows 本体を再起動しておく。

このインストール方法、一体いつまで使えるのだろうか。。。

とりあえず、ファイルは4年前と同じここにあった。
`C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\VC\Auxiliary\Build`

![vcvarsall.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/0480109d-fa29-f479-42af-b772e1df50e8.png)

昔と変わらず。


### 2019 ※使ってません

ここからダウンロードしたインストーラでインストールできます

https://visualstudio.microsoft.com/ja/vs/older-downloads/

※ただし、2019を使った解決策はまだ確立できておらず、この記事にも書いていません。


## 3. Cythonコードの書き換え

(swig 実行時エラーへの対処場合、やらなくてよさそう？)

Jupyter Notebook上のエラーログには、`_msvccompiler.py`というファイルの135行目付近でエラーが発生したと書かれていたので、まずはそのファイルを探す。

ここにあった

- anaconda で環境構築してた時  
`C:\Users\PCUser\Anaconda3\Lib\distutils\_msvccompiler.py`
- pyenv-win で環境構築した時  
`C:\Users\PCUser\...\.venv\Lib\site-packages\setuptools\_distutils\_msvccompiler.py`

次に、`_msvccompiler.py`の処理を追って行くと、次のメソッドで`vcvarsall`が`None`になっているためにエラーが発生していることが判明。

```python:_msvccompiler.py
# 135行目付近
def _get_vc_env(plat_spec):
    if os.getenv("DISTUTILS_USE_SDK"):
        return {
            key.lower(): value
            for key, value in os.environ.items()
        }

    vcvarsall, vcruntime = _find_vcvarsall(plat_spec)
    if not vcvarsall:
        # Jupyter Notebook上ではこのエラーが見える
        raise DistutilsPlatformError("Unable to find vcvarsall.bat")

    # ... 以下略
```
まずは error 発生直前の`_find_vcvarsall`メソッドが怪しい。
このメソッドを探すと....

```python:_msvccompiler.py
# 92行目付近
def _find_vcvarsall(plat_spec):
    _, best_dir = _find_vc2017()
    vcruntime = None
    vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
    if best_dir:
        vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**",
            "Microsoft.VC141.CRT", "vcruntime140.dll")
        try:
            import glob
            vcruntime = glob.glob(vcredist, recursive=True)[-1]
        except (ImportError, OSError, LookupError):
            vcruntime = None

    # ... 以下略
```
`_find_vcvarsall`メソッド内に`_find_vc2017()`なるメソッドを発見。

こいつの戻り値`best_dir`さえ正しければ、`vcvarsall.bat`ファイルも見つけられる気がするのだけれど...

`print(best_dir)`したら`None`が入ってました。
ぷわー(/・ω・)/

ここで3時間以上経過していたため、完全に面倒くさくなり、`best_dir`に直接pathを代入させることにしました...

```python:_msvccompiler.py
# 92行目
def _find_vcvarsall(plat_spec):
    # _, best_dir = _find_vc2017()  # もともとあったコード

    # INFO: from this line, cord is added ...
    # tmp_path に正しいpathを投入
    tmp_path = "C:/Program Files (x86)/Microsoft Visual Studio/2017/WDExpress".replace('/', os.sep)
    best_dir = os.path.join(tmp_path, "VC", "Auxiliary", "Build")
    print(best_dir)
    # INFO: until this line, cord is added ...

    vcruntime = None
    vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
    if best_dir:
        vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**",
            "Microsoft.VC141.CRT", "vcruntime140.dll")
        try:
            import glob
            vcruntime = glob.glob(vcredist, recursive=True)[-1]
        except (ImportError, OSError, LookupError):
            vcruntime = None

    # ... 以下略
```

この修正を行った後に再度実行したところ、めでたくCythonによるコンパイルが成功しました！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327407/33b8c481-339d-9178-726e-9de4138003bd.png)


## 補足事項
`_msvccompiler.py`を書き換える度に、Jupyter Notebook の kernel を restart する必要があります。
そうしないと、たとえファイルを書き換えても実行結果が変わりませんでした。

# 参考にした記事
* WindowsでもCythonを使いたい！  

https://qiita.com/kei0425/items/d9ff587e882c25e2a7e6

-> Visual Studioが必要と判明

* 「Unable to find vcvarsall.bat」の対処法

https://www.regentechlog.com/2014/04/13/build-python-package-on-windows/  

-> Pythonをコンパイルした`VC++`のバージョンが重要な意味を持つと判明

* Fix Python 3 on Windows error Microsoft Visual C++ 14.0 is required  

https://www.scivision.dev/python-windows-visual-c-14-required/  

-> python3.7にはVisual Studio 2017が必要と判明

* Installing Cython For Anaconda On Windows (※link切れ)  

https://www.ibm.com/developerworks/community/blogs/jfp/entry/Installing_Cython_On_Anaconda_On_Windows?lang=en  

-> Cythonのソースを直接書き換えるという愚行を試すきっかけ

* VisualStudio2017 install 方法 (2023/10/01 現在有効)

https://qiita.com/toras9000/items/42b5a2c093a0dba1d8b7

もし、この記事がなかったら、2023年時点において VisualStudio2017 はインストールできなかった。。


# 惜しくも参考にできなかった記事

多分こっちのやり方が正統派なんだと思いますです...
[CythonExtensionsOnWindows](https://github.com/cython/cython/wiki/CythonExtensionsOnWindows)
私の力では、何をすればいいのかわかりませんでした(´・ω・`)