python 3.7.0の仮想環境において、cx_Freezeでバイナリが作れない件

## 概要
pyenv-virtualenvで構築したpython3.7.0仮想環境で、cx_Freeze(5.1.1)を使ってバイナリを作ろうとしたのですが失敗したので、その対処法をまとめました。  

**注意：**本件のエラーはおそらく一時的なものなので、インストールしたモジュールを直接書き換えるというその場しのぎ的な対応になります。(2018年11月18日現在)
また、本記事では、仮想環境にインストールしたパッケージを触るだけなので、`/usr/bin`に入っているpythonの環境や他に構築した仮想環境には影響がないのでご安心ください。

##  エラーが出るまで
### 仮想環境の構築
使用している環境としては、Linux(CentOS7),64bitです。
python3.7.0 の仮想環境の構築は[pyenv と pyenv-virtualenv で環境構築](https://qiita.com/Kodaira_/items/feadfef9add468e3a85b)を参照して作りました。
構築した仮想環境は、`python3.7.0`で`new_3.7.0` という名前で作りました。

### バイナリの作成
バイナリの作成には、`cx_Freeze(5.1.1)`を`pip`でインストールしてやりました。
[【python】Python スクリプトを実行ファイルに変換する方法](https://www.web-cte.co.jp/kozakuralab/?p=1462)あたりを参考に作りました。

### バイナリの実行/エラーの発生
作成したバイナリを実行すると次のようなエラーが出力されました。

```
(new_3.7.0)$ ./my_package
Fatal Python error: initfsencoding: unable to load the file system codec
ImportError: invalid flags 1536114382 in 'encodings'

Current thread 0x00007f6f3ffa9740 (most recent call first):
中止 (コアダンプ)
```
ちなみに`python3.6.6`で構築した仮想環境で作成したバイナリでは同様のエラーは確認されませんでした。

## 解決
しばらく調べてみていると分かったことが、本事案につきまして[被害者は多数いる模様](https://github.com/anthony-tuininga/cx_Freeze/issues/399)でした。

[What could be the reason for fatal python error:initfsencoding:unable to load the file system codec?](https://stackoverflow.com/questions/51183280/what-could-be-the-reason-for-fatal-python-errorinitfsencodingunable-to-load-th)
によれば、

>Fixed in [4c18633](https://github.com/Alexpux/MINGW-packages/commit/4c18633ba2331d980f00aff075f17135399c43c5). The problem is that for version x64 for Python 3.7 it is not working due to class config for freezer.py. You need to go cx_Freezer installation folder. If you have a virtual environment, go to your environment folder `\lib\site-packages\cx_Freeze`, find the freezer.py and add the code found on the commit.
>
>You can find the patch at GitHub

なんと。
環境フォルダにある`freezer.py`を[4c18633](https://github.com/Alexpux/MINGW-packages/commit/4c18633ba2331d980f00aff075f17135399c43c5)にある通りに、書き直しなさいとのこと。
やってみましょう。

### 仮想環境内のcx_Freezerを触る
仮想環境の中でインストールしたモジュールは次のコマンドで確認します。

```
(new_3.7.0)$ pip show cx_freeze
Name: cx-Freeze
Version: 5.1.1
Summary: create standalone executables from Python scripts
Home-page: https://anthony-tuininga.github.io/cx_Freeze
Author: None
Author-email: None
License: Python Software Foundation License
Location: /#home_dir#/.pyenv/versions/3.7.0/envs/new_3.7.0/lib/python3.7/site-packages
Requires:
Required-by:
```

`/#home_dir#/.pyenv/versions/3.7.0/envs/new_3.7.0/lib/python3.7/site-packages`にあるみたいなので、移動して確認します。

```
(new_3.7.0)$ ls
__init__.py  dist.py     initscripts  setupwriter.py
__pycache__  finder.py   macdist.py   util.cpython-37m-x86_64-linux-gnu.so
bases        freezer.py  main.py      windist.py
common.py    hooks.py    samples
```
なるほど。`freezer.py`を触ったらいいみたいです。

修正箇所は、`L550`以降のこの領域を全て削除して、

```python:freezer.py
            # the file is up to date so we can safely set this value to zero
            if module.code is not None:
                if module.file is not None and os.path.exists(module.file):
                    mtime = os.stat(module.file).st_mtime
                else:
                    mtime = time.time()
                if sys.version_info[:2] < (3, 3):
                    header = magic + struct.pack("<i", int(mtime))
                else:
                    header = magic + struct.pack("<ii", int(mtime), 0)
                data = header + marshal.dumps(module.code)
```
次のコードに書き換えてください。

```python:freezer.py
            # the file is up to date so we can safely set this value to zero
            if module.code is not None:
                if module.file is not None and os.path.exists(module.file):
                    stat = os.stat(module.file)
                    mtime = stat.st_mtime
                    size = stat.st_size & 0xFFFFFFFF
                else:
                    mtime = time.time()
                    size = 0
                if sys.version_info[:2] < (3, 3):
                    header = magic + struct.pack("<i", int(mtime))
                elif sys.version_info[:2] < (3, 7):
                    header = magic + struct.pack("<ii", int(mtime), 0)
                else:
                    header = magic + struct.pack("<iii", 0, int(mtime), size)
                data = header + marshal.dumps(module.code)
```

### ビルド/実行

`python setup.py build`を実行してビルドします。
そして、バイナリが作られているのを確認して実行します。
するとまあ、なんということでしょう。

```
(new_3.7.0)$ ./my_package

Start analysis
...
```
そこには、元気に走り回るパッケージ君の姿が！！

## 最後に
こんな感じで、cx_Freezeがまだ更新されていないようなので、今回はこのようにコミットされていたパッチを参考に、仮想環境にインストールしたモジュールに直接書き込むことで、その場しのぎの緊急対応ができました。
ただ、モジュール自体に手を加えるのは想定外のバグをまねく可能性が大きいため、今回のようにあらかじめ構築した仮想環境上で、編集を加えた方がリスクが少なくります。
`cx_Freeze`が更新されて、python3.7.0でも問題なく実行できるよう、
1日でも早くこの記事が闇に葬り去られる日が来ることを祈ります。

もし、記事に間違っている点やわからない点がありましたらコメントくださると幸いです。

お読みくださいましてありがとうございました。