Windows上の Python-3.11 で動く Gtk4 や GStreamer をビルドする

試行錯誤の結果、ビルド手順を編み出した。

:::note

MSYS, MINGW は使わずに MSVC だけで完結させる例です。
:::

# 環境と関連ツールのインストール

インストール先のフォルダを決めて prefix と呼びます。
`C:\gnome` にしました。


* Windows11(64bit)
* vc build tool 2022
  * https://visualstudio.microsoft.com/ja/visual-cpp-build-tools/
* python-3.11 => `C:/Python311`
  * pip install meson => `C:/Python311/Scripts/meson.exe`
* cmake-3.22.1(Android SDK のやつにパス通した。meson が使っている？無くても動くかも)
* ninja(Android SDK の cmake と同じパスに入っているやつ)
* [win_flex](https://sourceforge.net/projects/winflexbison/files/) win_flex_bison3-latest.zip パス通す
* [pkg-config lite](https://sourceforge.net/projects/pkgconfiglite/files/) パス通す
  * 環境変数 PKG_CONFIG_PATH を設定。場所は `prefix` + `lib\pkgconfig`

:::note info
最初からやり直すときは

C:/gnome (prefix)
pygobject/build
gtk/build
gstreamer/build

を削除してください。
完全に掃除するには、

pygobject/subprojects
gtk/subprojects
gstreamer/subprojects

の clone されたりアーカイブが解凍されたフォルダも消してください。
:::

:::note info
meson の subprojects

最近の GTK 周りではビルドに [meson](https://mesonbuild.com/) が使われています。
meson には、システムのライブラリ(zlibなど)をサーチ(pkg-config)して見つかればそれを使うし、
なければダウンロードして自前でビルドするという機能が備わっています。

https://mesonbuild.com/Wrapdb-projects.html

A + subproject B という構成があったときに、
以下の２つで、わりと異なる結果になります。

```
install A(subproject B を自動インストール)
```

```
install B
install A
```

そのためインストール順によって、うまくいったりいかなかったりしました。
:::

# PYTHONUTF8 とロケールの組み合わせ

`Windows11(64bit) + python-3.11で実験`

|環境変数 PYTHONUTF8=1|日本語Windowsの utf-8 ロケール||
|-|-|-|
|あり|あり|動く|
|あり|なし|UnicodeDecodeError|
|なし|なし|動く|
|なし|あり|試していない|

:::note warn
ロケール周りの挙動

PYTHONUTF8=1 で `subprocess` 系の挙動が変わる。
一方、日本語Windowsの utf-8 ロケールで VC のマルチバイト文字列のエンコーディングの挙動が変わる。

```python:C:/Python311/Lib/locale.py
    def getpreferredencoding(do_setlocale=True):
        """Return the charset that the user is likely using,
        according to the system configuration."""

        if sys.flags.warn_default_encoding:
            import warnings
            warnings.warn(
                "UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead.",
                EncodingWarning, 2)
        if sys.flags.utf8_mode: # 👈 環境変数 PYTHONUTF8 と連動？
            return 'utf-8'
```

設定が一致しないと meson が pipe 経由でコンパイラ等を操作したときに例外が発生します。

```
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8e in position 115: invalid start byte
```

:::

https://qiita.com/methane/items/9a19ddf615089b071e71

https://qiita.com/nanbuwks/items/8d6c5685b1d4d70c058b


# ビルド

本記事では、

* pygobject(GLib, gobject-introspection etc...)
* gtk(cairo, gtk, gdk etc...)
* gstreamer

という順でインストールします。

## pygobject

:::note warn
初手で pygobject

何故か cairo -> pygobject のように subproject を分けるとビルドがうまくいかない。
例えビルドできても python から GLib を参照したときに型情報の取得に失敗したりする。
初手で pyproject にするとうまくいく。
pyproject -> gobject-introspection -> glib という subproject 連鎖の
どこかに違いがありそう。
:::

```
> git clone https://gitlab.gnome.org/GNOME/pygobject.git
> cd pygobject
pygobject> meson setup build --prefix C:/gnome -Dpycairo=disabled -Dtests=false
pygobject> meson compile -C build # make のように省略可
pygobject> meson install -C build
```

## pygobject エラー対応

### ImportError: DLL load failed while importing _giscanner: 指定されたモジュールが見つかりません
python-3.7 以前ではこの手順は不要です。

:::note alert
Windows版 python-3.8 の動的ライブラリの挙動変更

これは native module のロードが失敗するというかたちで現れます。

[What’s New In Python 3.8 — Python 3.8.14 documentation](https://docs.python.org/3.8/whatsnew/3.8.html#changes-in-the-python-api)

> `PATH` and `the current working directory` are no longer used

:::

環境変数 `GI_EXTRA_BASE_DLL_DIRS` に関連する dll のディレクトリを指定してやればよい。

:::note
本来ならば C:/gnome/bin に配置される dll が、まだビルドしたけどインストールしていないので build フォルダに存在している
:::

dll をサーチして、そのフォルダを ";" で連結した文字列を作る python スクリプト例。

```python:dll_dir.py
import pathlib
from typing import List

HERE = pathlib.Path(__file__).absolute().parent

class DllAggregator:
    def __init__(self) -> None:
        self.dll_list: List[pathlib.Path]=[]

    def process(self, path: pathlib.Path):
        for e in path.iterdir():
            if e.is_dir():
                self.process(e)
            elif e.suffix.lower() == '.dll':
                self.dll_list.append(e)


if __name__ == '__main__':
    dll_list = DllAggregator()
    dll_list.process(HERE)

    dir_list=[]
    for e in dll_list.dll_list:
        if e.parent not in dir_list:
            dir_list.append(e.parent)

    print(';'.join(str(e) for e in dir_list))
```

power shell で環境変数をセットする例
```powershell
$env:GI_EXTRA_BASE_DLL_DIRS="C:/path/to/build/hoge;C:/path/to/build/fuga" # python の os.add_dll_directory に渡る。たぶん "/" と "\" どちらでも動く。
```

## pygobject 動作確認

:::note
pth ファイルで c:/gnome/lib/site-packages を追加する

```pth:c:/python311/lib/site-packages/gi.pth
c:/gnome/lib/site-packages
```

もしくは環境変数

```
PYTHONPATH=c:/gnome/lib/site-packages
```

:::

```py
import gi
gi.require_version('GLib', '2.0')
from gi.repository import GLib # うまくインストールされていないとここでエラーになる。 assert g_type != TYPE_NONE
print(GLib.MainLoop)
```

:::note alert
DLL load failed while importing _gi: 指定されたモジュールが見つかりません。

`python-3.8 以降向け`
nativie モジュールを import する前の行にハードコーディングします。

```py:C:\gnome\lib\site-packages\gi\__init__.py
os.add_dll_directory('C:/gnome/bin') # python-3.8 以降で必要
from . import _gi
```
:::

エラーが出なければ OK。

## gtk4

```
$ git clone https://github.com/GNOME/gtk.git
$ cd gtk
gtk$ meson setup build --prefix C:/gnome -Dmedia-gstreamer=disabled -Dbuild-tests=false
gtk$ meson install -C build
```

## gtk4 動作確認

```py
import gi
gi.require_version('Gtk', '4.0') # 👈 4.0
from gi.repository import Gtk
```

エラーが出なければインストールできているはず。
以上で Python-3.11 + Gtk4 成功です。

## gtk3
gtk-3 も問題なくビルドできて　gtk-4 と共存できた。
3系の最新版 gtk-3.24 を入手。

https://github.com/GNOME/gtk/releases/tag/3.24.33

解凍してビルド。

```
> meson setup build --prefix C:/gnome
> meson install -C build
```

## gtk3 エラー対応
### Couldn't find include 'Atk-1.0.gir'

途中 Atk の gir が見つからないというエラーが出て止まる。
Atk を先に入れる。

:::note info
meson により、gtk-3 のビルド過程で subprojects/atk.wrap により subprojects/atk にソースが展開されています。
:::

```
> cd subprojects/atk
subprojects/atk> meson setup build --prefix C:/gnome
subprojects/atk> meson install -C build
```

元のフォルダに戻る。続きを実行。

```
> meson install -C build
```

## gtk3 動作確認

```py
import gi
gi.require_version('Gtk', '3.0') # 👈 3.0
from gi.repository import Gtk
```

## gstreamer

ビルド済みの gstreamer と比べて、 dshow 系の elment が増えるので Windows ではビルドすると使い勝手が良くなる。

## gstreamer エラー対応

### dshowvideosink.cpp でコンパイルエラー

適当に cast してあげる。

```cpp
      SendMessage (previous_window, WM_CLOSE, (WPARAM)NULL, (LPARAM)NULL);
    SystemParametersInfo (SPI_GETWORKAREA, (UINT)NULL, &rect, 0);
    SendMessage (sink->window_id, WM_CLOSE, (WPARAM)NULL, (LPARAM)NULL);
```

## gstreamer 動作確認

```py
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
Gst.init(None)
```

## Vala 言語

https://github.com/ZacharyJia/vala-win

fork して CMakeLists.txt 改造。
オリジナルが vala-0.48 向けだったのだけど、
新しい vala-0.56 でもそのまま動いてしまった。

TODO: cmake を meson 化する

https://github.com/ousttrue/vala-win/tree/v0.56

上記でビルドした glib, gobject-introspection に対してビルドできた。

```
valac --version
Vala 0.56.3
```

動くかな？

`libadwaita` ビルドできなかった。ちゃんと動いていない。

# その他のエラー対応
### TypeError: Couldn't find foreign struct converter for 'cairo.Context' 
`pycairo` が入っていない。
cairo の後で pygobject をインストールすることでインストールされる。

```
pygobject> rm build
pygobject> meson setup build --prefix c:/gnome
pygobject> meson install -C build
```

エラー発生

```
FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。: 'C:\\ghq\\github.com\\GNOME\\pygobject/build\\subprojects\\proxy-libintl'

# 消し忘れ。環境変数クリア。
$env:GI_EXTRA_BASE_DLL_DIRS=""
```

### referenced by the typelib: 'cairo-gobject.dll': 指定されたモジュールが見つかりません。

`cairo-gobject-2.dll` ?

# インストール順
行きつ戻りつしつつ同じライブラリのスキップされた要素を再インストールしていくこともある。

* gobject-introspection > glib
* pygobject > cairo, gobject-introspection
* gtk > cairo, gobject-introspection
* gst-python > gobject-introspection
* gtk の media-gstreamer > gstreamer

subproject の個別インストールと順番の調整は改善の余地あり。

依存関係を考慮して meson の呼び出しを制御するツールを作ったほうがいいかもしれない。
clone, patch, meson を繰り返す。

https://github.com/wingtk/gvsbuild

# meson のデバッグ実行

python なのでデバッガをアタッチできる。
vscode の設定例。

```json:.vscode/settings.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "meson",
            "type": "python",
            "request": "launch",
            "module": "mesonbuild.mesonmain", // 👈 meson モジュールのエントリーポイント
            "args": [
                "setup", // 👈 作業で使いたい引数を指定する
                "build",
                "--prefix",
                "c:/gnome"
            ],
            "cwd": "${workspaceFolder}",
            "justMyCode": false // 👈 重要。.venv の中で break できる
        },
    ]
}
```

:::note
venv に meson 入れる

meson 自体に手を入れたり、print debug する際に。

```
> py -m venv .venv
> .venv/Scripts/Activate.ps1
(.venv)> pip install meson
```
:::

# 参考

https://www.collabora.com/news-and-blog/blog/2021/03/18/build-and-run-gtk-4-applications-with-visual-studio/

# 続編

https://qiita.com/ousttrue/items/52af61491fcf92c989f4