Python / matplotlib / nuscenes-devkit] debian 系の Docker で matplotlib<3.6 or nuscenes-devkit のインストールに失敗する時の対策

# 1. はじめに

`matplotlib<3.6` や `nuscenes-devkit` のインストールに詰まったため、この問題の対策を記事にしました。 matplotlib<3.6 だけの人のお役にも立てると思います。

## 1-1. nuscenes-devkit について

`nuscenes-devkit` は、　[nuscenes](https://www.nuscenes.org/) というデータセットを扱う時の補助ツールです。 nuscenes 自体は、車載カメラやLiDARなどのデータを使って道路上の物体検出などを行うタスクのためのベンチマークデータセットとなっていて、 nuscenes-devkit を使うことで、複雑なデータ構造になっている nuscenes をとても扱いやすくなります。

タスク的には `3D Object Detection` や `LIDAR Semantic Segmentation` などに使えるデータセットです。

## 1-2. 対処した問題

この `nuscenes-devkit` をインストールしようとした時に、依存モジュールである `matplotlib<3.6` のインストールに詰まりました。

以下の2つの問題が発生していました。

1. GNU make (>= 3.80) or makepp (>= 1.19) is required to build FreeType2.
2. ImportError: libgthread-2.0.so.0: cannot open shared object file: No such file or directory


# 2. 環境

エラー発生時の利用環境は以下の通り。

item|version
--|--
Docker image|python:3.8.20-slim
OS (Docker の OS) | Debian GNU/Linux 12 (bookworm)
Python|3.8.20
matplotlib|3.5.3
nuscenes-devkit|1.1.11

ちなみに matplotlib の 3.9.2 であればスムーズにインストールできました。
あくまでも、 `nuscenes-devkit==1.1.11` が要求している `matplotlib<3.6` をインストールしようとするとエラーが発生する、という状況でした。


# 3. エラーへの対処

## 3-1. GNU make (>= 3.80) or makepp (>= 1.19) is required to build FreeType2.

### 3-1-1. エラー状況

以下のようなエラーが発生していた。

```bash
$ pip install nuscenes-devkit

... (省略)

      creating tmp
      gcc -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/usr/local/include/python3.11 -c /tmp/tmp0jqv7oca.cpp -o tmp/tmp0jqv7oca.o -fvisibility=hidden
      gcc -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/usr/local/include/python3.11 -c /tmp/tmpyypvhlnd.cpp -o tmp/tmpyypvhlnd.o -fvisibility-inlines-hidden
      gcc -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/usr/local/include/python3.11 -c /tmp/tmpqtm9sjja.cpp -o tmp/tmpqtm9sjja.o -flto
      Extracting /project/freetype/freetype2/2.6.1/freetype-2.6.1.tar.gz
      Building freetype in build/freetype-2.6.1
      GNU make (>= 3.80) or makepp (>= 1.19) is required to build FreeType2.
      Please try
      
        MAKE=<GNU make command name> ./configure
      
      or
      
        MAKE="makepp --norc-substitution" ./configure
      Traceback (most recent call last):
        File "<string>", line 2, in <module>
        File "<pip-setuptools-caller>", line 34, in <module>
        File "/tmp/pip-install-uv6oa0j1/matplotlib_bb137b8506fb4cb9ab4d9758fe115fc2/setup.py", line 273, in <module>
          setup(  # Finally, pass this all along to setuptools to do the heavy lifting.
        File "/usr/local/lib/python3.11/site-packages/setuptools/__init__.py", line 87, in setup
          return distutils.core.setup(**attrs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/core.py", line 185, in setup
          return run_commands(dist)
                 ^^^^^^^^^^^^^^^^^^
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/core.py", line 201, in run_commands
          dist.run_commands()
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/dist.py", line 968, in run_commands
          self.run_command(cmd)
        File "/usr/local/lib/python3.11/site-packages/setuptools/dist.py", line 1217, in run_command
          super().run_command(command)
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/dist.py", line 987, in run_command
          cmd_obj.run()
        File "/usr/local/lib/python3.11/site-packages/wheel/_bdist_wheel.py", line 378, in run
          self.run_command("build")
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/cmd.py", line 319, in run_command
          self.distribution.run_command(command)
        File "/usr/local/lib/python3.11/site-packages/setuptools/dist.py", line 1217, in run_command
          super().run_command(command)
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/dist.py", line 987, in run_command
          cmd_obj.run()
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/command/build.py", line 132, in run
          self.run_command(cmd_name)
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/cmd.py", line 319, in run_command
          self.distribution.run_command(command)
        File "/usr/local/lib/python3.11/site-packages/setuptools/dist.py", line 1217, in run_command
          super().run_command(command)
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/dist.py", line 987, in run_command
          cmd_obj.run()
        File "/usr/local/lib/python3.11/site-packages/setuptools/command/build_ext.py", line 84, in run
          _build_ext.run(self)
        File "/usr/local/lib/python3.11/site-packages/setuptools/_distutils/command/build_ext.py", line 346, in run
          self.build_extensions()
        File "/tmp/pip-install-uv6oa0j1/matplotlib_bb137b8506fb4cb9ab4d9758fe115fc2/setup.py", line 187, in build_extensions
          package.do_custom_build(env)
        File "/tmp/pip-install-uv6oa0j1/matplotlib_bb137b8506fb4cb9ab4d9758fe115fc2/setupext.py", line 643, in do_custom_build
          subprocess.check_call(configure, env=env, cwd=src_path)
        File "/usr/local/lib/python3.11/subprocess.py", line 413, in check_call
          raise CalledProcessError(retcode, cmd)
      subprocess.CalledProcessError: Command '['./configure', '--with-zlib=no', '--with-bzip2=no', '--with-png=no', '--with-harfbuzz=no', '--enable-static', '--disable-shared', '--host=aarch64-unknown-linux-gnu']' returned non-zero exit status 1.
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for matplotlib
  Running setup.py clean for matplotlib
  Building wheel for Shapely (pyproject.toml) ... done
  Created wheel for Shapely: filename=Shapely-1.8.5.post1-py3-none-any.whl size=183034 sha256=64d08f04a57439497a7ba15153c83dc477a02799483bcfa0051f920989353b40
  Stored in directory: /root/.cache/pip/wheels/e6/72/c9/6efe4677eafc32ab9670e71abaf8feb09285c51f7ba178abbb
  Building wheel for fire (setup.py) ... done
  Created wheel for fire: filename=fire-0.7.0-py3-none-any.whl size=114248 sha256=96ced4ca9577991cc6c374c44cf8db8676665a7af8b4c51ed92334230ea2e049
  Stored in directory: /root/.cache/pip/wheels/46/54/24/1624fd5b8674eb1188623f7e8e17cdf7c0f6c24b609dfb8a89
Successfully built Shapely fire
Failed to build matplotlib
ERROR: ERROR: Failed to build installable wheels for some pyproject.toml based projects (matplotlib)
```

なんか matplotlib をインストールできてないっぽい。ほげー。

怪しいのは、この辺。

```bash
      GNU make (>= 3.80) or makepp (>= 1.19) is required to build FreeType2.
      Please try
      
        MAKE=<GNU make command name> ./configure
      
      or
      
        MAKE="makepp --norc-substitution" ./configure
```


### 3-1-2. 解決策

対策として、以下のコマンドを実行して解決した。

```bash
$ apt update
$ apt install build-essential
$ pip install nuscenes-devkit
```

docker コンテナを build し直す人は `--no-cache` をつけるのを忘れないでね。
少なくとも、これで install には成功する。


### 3-1-3. (参考) 上手くいかなかった対処法

以下2つの記事に書かれていた情報を見たんだけど、これらの対処法はダメっぽかった。

https://github.com/matplotlib/matplotlib/issues/12169/#issuecomment-621708209

https://blog.saladbowl.work/blog/how-to-install%20matplotlib-ubuntu

```bash
# これだとダメだった
$ apt update
$ apt install libfreetype6-dev libxft-dev
$ pip install matplotlib==3.5.3
```


## 3-2. ImportError: libgthread-2.0.so.0: cannot open shared object file: No such file or directory


これは、 install した `nuscenes-devkit` を利用できない問題。
matplotlib の問題だけで困っていた人は、ここから先は関係ありません。

### 3-2-1. エラー状況

`nuscenes-devkit` を利用しようとしたところ、以下のエラーが発生しました。

```python
from nuscenes.map_expansion.map_api import NuScenesMap, NuScenesMapExplorer

ImportError: libgthread-2.0.so.0: cannot open shared object file: No such file or directory
```

### 3-2-2. 解決策

この問題は `libglib2.0-0` というパッケージをインストールすることで解決できました。


```bash
$ apt update
$ apt install -y libglib2.0-0
```

これでOK(\*˘︶˘\*).｡.:*


# 4. 参考にした記事

## 4-1. Red-Hat 系列における同じ問題の対策

https://stackoverflow.com/questions/68767280/lambda-docker-base-unable-to-install-matplotlib

この記事には、Red-Hat 系列の OS で同様のエラーが発生した場合の対策が記載されていました。

## 4-2. `build-base` と同等かつ apt でインストールできるパッケージの探索

`build-base` なんてツールは apt ではインストールできなかったので、以下の記事を見ていたところ、 `build-essential` なるものが apt でインストールできると書かれていることを発見。

https://github.com/gliderlabs/docker-alpine/issues/24

というわけで、 `build-essential` を試しに入れてみたところ、上手くいった、という具合でした。


## 4-3. docker の apt update をやり直す

キャッシュされている layer を使われてしまうと、 apt update が再実行されなかったりして困る。
そんな時の対策は以下に書かれていた。

https://qiita.com/seigot/items/a896807a22906225105b


## 4-4. libgthread-2.0.so.0 が見つからない問題の対処

https://hacker.trickstorm.com/?p=1954