pythonを実行ファイル化するWindows・Linux対応(PyInstaller・Docker・Github Actions)

pythonコードをWindowsはexeファイル、Linuxはバイナリファイルにビルドしてpythonが無い環境でも実行できる様にします。ビルドにはDocker + Github Actionsを使用するので自分でWindowsや各Linux環境を用意する必要はありません。

# 作るもの・完成形のリポジトリ

完成したもの以下のリポジトリにおいてあるのでそのまま自分のGithubリポジトリに複製すればGithub Actionsでビルドが実行されます。

- https://github.com/k8shiro/exec_command_app

上記リポジトリは以下の様になっています。`src/main.py`が実行ファイル化対象のpythonコードです。各Github Actionのworkflow(`.github/workflows/centos7.yml・linux.yml・windows.yml`)のアーティファクトとしてビルドした実行ファイルをダウンロードできます。

```
├── README.md
├── .github
│   └── workflows
│       ├── centos7.yml
│       ├── linux.yml
│       └── windows.yml
├── centos7
│   └── Dockerfile
├── linux
│   └─── Dockerfile
├── src
│   └── main.py
└── windows
    ├── Dockerfile
    └── dist
```

# Linux用の実行ファイル生成

`linux/Dockerfile`を使用してbuildします。Dockerfileの中身はpythonとpyinstallerの用意だけをしています。

```
FROM python:3.8-buster

# ホストマシンのコードをコンテナにコピー
COPY ./src /src
WORKDIR /src

# 依存関係のインストール
RUN pip install --no-cache-dir pyinstaller
```
あとはこのDockerfileを使ってビルドするworkflowを`.github/workflows/linux.yml`で定義しています。

```
name: Build for Linux

on:
  push:
    branches:
      - main

jobs:
  build-and-run:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Build Docker image
        run: docker build  -f ./linux/Dockerfile -t pyinstaller-linux .

      - name: Run Docker container
        id: run-container
        run: |
          docker run --rm -v $(pwd)/src:/src -v $(pwd)/linux:/linux \
              pyinstaller-linux pyinstaller main.py \
              --onedir --onefile --clean \
              --distpath /linux/dist \
              --workpath /linux/build \
              --specpath /linux 

      - name: Upload artifact
        uses: actions/upload-artifact@v2
        with:
          name: result-artifact
          path: ./linux/dist/main
```

やっていることは
- リポジトリチェックアウト
- Dockerfileのビルド
- ビルドしたDockerイメージでpyinstaller実行しpythonコードをビルド
- 生成した実行ファイルをアーティファクトとしてアップロード

になります。

# Centos7用の実行ファイル生成 

上記linux用に生成した実行ファイルをCentos7の上で実行しようとすると

```
[1427] Error loading Python lib '/tmp/_MEIWviqWy/libpython3.8.so.1.0': dlopen: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /tmp/_MEIWviqWy/libpython3.8.so.1.0)
```
となります。これはビルドに使用したDockerイメージのGLIBCのバージョンが新しく、Centos7のGLIBCのバージョンが古いために起こっています。同様に他の古いLinux環境でも同じ問題が起こることがあります。

そのため、Centos7用のDockerfileでは`centos/python-38-centos7`を使用しています。その他手順はLinux用と同じです。

```
FROM centos/python-38-centos7

USER root

# ホストマシンのコードをコンテナにコピー
COPY ./src /src
WORKDIR /src

# 依存関係のインストール
RUN pip install --no-cache-dir pyinstaller
```

Github Actionsのworkflowはほぼ同じです。

```
name: Build for Centos7

on:
  push:
    branches:
      - main

jobs:
  build-and-run:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Build Docker image
        run: docker build  -f ./centos7/Dockerfile -t pyinstaller-centos7 .

      - name: Run Docker container
        id: run-container
        run: |
          docker run --rm -v $(pwd)/src:/src -v $(pwd)/centos7:/centos7 \
              pyinstaller-centos7 pyinstaller main.py \
              --onedir --onefile --clean \
              --distpath /centos7/dist \
              --workpath /centos7/build \
              --specpath /centos7 

      - name: Upload artifact
        uses: actions/upload-artifact@v2
        with:
          name: result-artifact
          path: ./centos7/dist/main
```

# Windows用の実行ファイル生成 

行うことはLinuxと基本的に同じですが、Windows用のexeファイル生成にはWindows環境が必要であり、ここではWindowsコンテナを使用しています。

```
FROM mcr.microsoft.com/windows/servercore:ltsc2019


# Chocolateyのインストール
RUN powershell -Command "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"

# Pythonのインストール
RUN powershell -Command "choco install -y python --version 3.8.10"


# ホストマシンのコードをコンテナにコピー
COPY ./src /app/src
WORKDIR /app/src

# 依存関係のインストール
RUN pip install --no-cache-dir pyinstaller

```

Github Actionsのworkflowも`runs-on: windows-2019`を指定し、Windowsの上で一連の処理を行います。またdockerコマンドの扱いも若干Linuxとは異なるので注意が必要です。

```
name: Build for Windows

on:
  push:
    branches:
      - main

jobs:
  build-and-run:
    runs-on: windows-2019

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Build Docker image
        run: docker build  -f ./windows/Dockerfile -t pyinstaller-windows .
      
      - name: Run Docker container
        run: docker run --rm  -v ${{ github.workspace }}\windows\dist\:C:\dist\ pyinstaller-windows powershell -Command "pyinstaller main.py --onedir --onefile --clean --distpath /dist --workpath /windows/build --specpath /windows"

      - name: Upload artifact
        uses: actions/upload-artifact@v2
        with:
          name: result-artifact
          path: ./windows/dist/main.exe

```

# 2023/06/29追記 Windowsのimage buildでPlease reboot the system and try to install/upgrade Chocolatey again.

以下のようなエラーでWindows用のimageのbuildで失敗するようになったので対応

```docker
Run docker build  -f ./windows/Dockerfile -t pyinstaller-windows .
Sending build context to Docker daemon    107kB

Step 1/6 : FROM mcr.microsoft.com/windows/servercore:ltsc2019
 ---> 0e4e89ebdca4
Step 2/6 : RUN powershell -Command "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"
 ---> Running in 876d98c09ce8
Forcing web requests to allow TLS v1.2 (Required for requests to Chocolatey.org)
Getting latest version of the Chocolatey package for download.
Not using proxy.
Getting Chocolatey from https://community.chocolatey.org/api/v2/package/chocolatey/2.0.0.
Downloading https://community.chocolatey.org/api/v2/package/chocolatey/2.0.0 to C:\Users\CONTAI~1\AppData\Local\Temp\chocolatey\chocoInstall\chocolatey.zip
Not using proxy.
Extracting C:\Users\CONTAI~1\AppData\Local\Temp\chocolatey\chocoInstall\chocolatey.zip to C:\Users\CONTAI~1\AppData\Local\Temp\chocolatey\chocoInstall
Installing Chocolatey on the local machine
The registry key for .Net 4.8 was not found or this is forced
Downloading 'https://download.visualstudio.microsoft.com/download/pr/2d6bb6b2-226a-4baa-bdec-798822606ff1/8494001c276a4b96804cde7829c04d7f/ndp48-x86-x64-allos-enu.exe' to 'C:\Users\CONTAI~1\AppData\Local\Temp\ndp48-x86-x64-allos-enu.exe' - the installer is 100+ MBs, so this could take a while on a slow connection.
Installing 'C:\Users\CONTAI~1\AppData\Local\Temp\ndp48-x86-x64-allos-enu.exe' - this may take awhile with no output.
.NET Framework 4.8 was installed, but a reboot is required. 
 Please reboot the system and try to install/upgrade Chocolatey again.
At C:\Users\ContainerAdministrator\AppData\Local\Temp\chocolatey\chocoInstall\t
ools\chocolateysetup.psm1:815 char:11
+           throw ".NET Framework 4.8 was installed, but a reboot is re ...
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (.NET Framework ...ocolatey ag 
   ain.:String) [], RuntimeException
    + FullyQualifiedErrorId : .NET Framework 4.8 was installed, but a reboot i 
   s required. 
 Please reboot the system and try to install/upgrade Chocolatey again.
```

Windows用のDockerfileを更新し`ENV chocolateyVersion=1.4.0`を指定するように対応。

```
FROM mcr.microsoft.com/windows/servercore:ltsc2019
ENV chocolateyVersion=1.4.0 # ここを追加

# Chocolateyのインストール
RUN powershell -Command "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"

# Pythonのインストール
RUN powershell -Command "choco install -y python --version 3.8.10"


# ホストマシンのコードをコンテナにコピー
COPY ./src /app/src
WORKDIR /app/src

# 依存関係のインストール
RUN pip install --no-cache-dir pyinstaller
```

# まとめ

WindowsとLinux環境それぞれで動くpythonコードの実行ファイル化の方法を紹介しました。また、一部の古い環境向けの対応方法としてCentos7用の手順も記載しています。今回は全部手順を揃えるためにDockerを使用してbuildを行うようにしましたが、Windowsコンテナは微妙に扱いづらく、試行錯誤するハメになりました。結局Github Actionsでビルドを実行するので、コンテナを使わない方が楽かもしれません。