Nuitkaで失敗しやすいポイントを解説

NuitkaはPythonのプログラムを実行可能ファイルにすることができるソフトです。
Nuitkaについてはこちらの記事が非常に参考になります。

https://blog.tsukumijima.net/article/python-nuitka-usage/

この記事ではNuitkaで失敗しやすいポイントを解説します。
バージョン0.7時点の情報です。

## 特定のモジュールを含めるようにする

`--follow-imports`オプションを使うと自動的に使用するモジュールをビルドしてくれますが、たまにうまく動作しない場合があります。
例えば`eth-hash`モジュールでは、動的に必要モジュールを読み込むためか、動作に必要な`eth_hash.backends.pycryptodome`をビルドしてくれません。
ビルドは通りますが、実行するとエラーになってしまいます。

https://github.com/ethereum/eth-hash/blob/fc3b9dda5f1ce5f908c7c0cbbbc63adb4e030da9/eth_hash/utils.py#L26

このような場合は、`--include-module`オプションを使用します。
```
--include-module=eth_hash.backends.pycryptodome
```

パッケージごと含めるようにするには、`--include-package`を使用します。
```
--include-package=uvicorn
```

## モジュールに必要なファイルを含めるようにする
モジュールによっては、pythonファイルの他に必要なファイルがあることがあります。

例えば、バージョンをversion.txtから読み込むようになっている場合、version.txtが無いとエラーになってしまいます。

https://github.com/espnet/espnet/blob/b714fbe578e1b40b36f1a06ff7bd9b8403ac1660/espnet/__init__.py

その場合は、`--include-data-file`を使用します。
例ではespnetモジュールですので、espnetフォルダ以下にversion.txtがコピーされるようにします。
```
--include-data-file="./venv/Lib/site-packages/espnet/version.txt=./espnet/"
```

パッケージ内のファイルを全て含めるようにするには、`--include-package-data`を使用します。
```
--include-package-data=pyopenjtalk
```

## プラグインを有効化する
いくつかのモジュールはプラグインを有効化する必要があります。
以下のコマンドでプラグインの一覧を確認できます。
```
$ python -m nuitka --plugin-list
                 The following plugins are available in Nuitka
--------------------------------------------------------------------------------
 anti-bloat        Patch stupid imports out of widely used library modules source codes.
 data-files
 dill-compat
 enum-compat
 eventlet          Support for including 'eventlet' dependencies and its need for 'dns' package monkey patching
 gevent            Required by the gevent package
 glfw              Required for glfw in standalone mode
 implicit-imports
 multiprocessing   Required by Python's multiprocessing module
 numpy             Required for numpy, scipy, pandas, matplotlib, etc.
 pbr-compat
 pkg-resources     Resolve version numbers at compile time.
 pmw-freezer       Required by the Pmw package
 pylint-warnings   Support PyLint / PyDev linting source markers
 pyqt5             Required by the PyQt5 package.
 pyside2           Required by the PySide2 package.
 pyside6           Required by the PySide6 package for standalone mode.
 pyzmq             Required for pyzmq in standalone mode
 tensorflow        Required by the tensorflow package
 tk-inter          Required by Python's Tk modules
 torch             Required by the torch / torchvision packages
```

`--plugin-enable`オプションをつけることでプラグインを有効化できます。

```
--plugin-enable=numpy
```

## どうしてもビルドできないモジュールを除外する
どうしてもビルドできないモジュールというものが出てきます。
しかし、実際の動作に不必要なモジュールだった場合、無理やり依存を外してビルドすることができます。
`--nofollow-import-to`オプションで特定のモジュールの自動インポート（ビルド）をやめることができます。
```
--nofollow-import-to=sklearn
```

このままでは、importしている所などでエラーが出てしまいますので、プラグインを自作して解決します。
以下の例では、librosaというモジュールが依存するsklearnというモジュールがビルドできず、かつ不要だったためインポートしないようにしています。

(fix_build.py)
```python
from nuitka.plugins.PluginBase import NuitkaPluginBase


class NuitkaPluginFixBuild(NuitkaPluginBase):
    plugin_name = "fix-build"

    @staticmethod
    def onModuleSourceCode(module_name, source_code):
        if module_name == "librosa.decompose":
            source_code = source_code.replace("import sklearn.decomposition", "#")
        elif module_name == "librosa.segment":
            source_code = source_code.replace("import sklearn", "#")
        return source_code
```

作ったプラグインを`--user_plugin`オプションで有効にします。

```
--user-plugin=fix_build.py
```

## Github Actionsでビルドを行う

ビルド中にyes/noを聞かれる場合があり、Github Actionsの場合そこでビルドが止まってしまいます。
`--assume-yes-for-downloads`オプションをつけることで対処できます。

## その他

公式マニュアル

https://nuitka.net/doc/user-manual.html

プラグインのマニュアル

https://github.com/Nuitka/Nuitka/blob/develop/Standard-Plugins-Documentation.rst

## 余談
この記事は、紹介した記事でも言及されているVOICEVOXというソフトの派生ソフトを作ったときの備忘録です。

https://github.com/VOICEVOX/voicevox_engine

一回のビルドに2、3時間かかるので大変でした。
ちなみにvoicevox_engineの`.github/workflows/build.yml`に自動ビルドの処理が書いてあるので参考になるかもしれません。
（nuitkaとは無関係な処理が多いですが...）