※当サイトはPRを含みます

Dockerコンテナをアイコン付きでそのままexe化する方法

2024年1月28日

やりたいこと

Ubuntu上でDockerコンテナを立てて開発したPythonコードを、Windowsアプリとしてアイコン付きでexe化したい!

サマリー

  1. windowsでPyinstallerを実行するコンテナ、docker-pyinstallerを使用して実行ファイルを作成(exe化)
  2. specファイルを修正して実行ファイルにアイコン付与

ディレクトリ構成

ディレクトリ構成です。

iconフォルダにはアプリのアイコン画像を格納します。

main.pyはアプリのソースコード。

requirements.txth、必要なライブラリを記載したテキストファイル。

.
├── icon  
│   └── icon.ico 
├── main.py 
└── requirements.txt

①exe化するコード “main.py" を作成

例としてtkinterを使ってGUIを作成し、画像を表示するアプリを作成する。

DALL-E3で作成した2匹のタヌキを表示するアプリです。

実行するとtkinterのGUI上に画像が表示されます。

# main.py
import tkinter as tk
from PIL import ImageTk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("画像の表示")       # ウィンドウタイトル
        self.master.geometry("500x500")     # ウィンドウサイズ(幅x高さ)

        # Canvasの作成
        self.canvas = tk.Canvas(self.master)
        # Canvasを配置
        self.canvas.pack(expand = True, fill = tk.BOTH)

        # 画像ファイルを開く
        self.photo_image = ImageTk.PhotoImage(file = "tanukis.png")

        # キャンバスのサイズを取得
        self.update() # Canvasのサイズを取得するため更新しておく
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()

        # 画像の描画
        self.canvas.create_image(
                canvas_width / 2,       # 画像表示位置(Canvasの中心)
                canvas_height / 2,                   
                image=self.photo_image  # 表示画像データ
                )

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

②必要なライブラリを記載した " requirements.txt " を作成

上記のmain.py と同じ階層に requirements.txt を作成する。

# requirements.txt 
pillow

③specファイルの作成

以下のコマンドを実行するとmani.pyを元にしたexeファイルとspecファイルが作成される。

docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows -c \
  "pip install -r requirements.txt && \
  pyinstaller main.py --onedir --onefile --noconsole --icon ./icon.ico && \
  mv dist/main.exe main.exe "

ここで作成されるmain.exeは問題なく動くが、起動時のウィンドウにアイコンが表示されないのでspecファイルを修正して再度作り直す必要がある。(④で説明)

コードの解説

docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows 

cdrx/pyinstaller-windowsイメージから使い捨てのコンテナを立て、カレントディレクトリをそのコンテナにマウントする。

pip install -r requirements.txt 

pipで依存関係をインストールする。

pyinstaller main.py --onedir --onefile --noconsole --icon ./icon.ico 

Pyinstallerでexe化する。

–onedir:出力を1ディレクトリにまとめる

–onefile:出力を1ファイルにまとめる

–noconsole:実行ファイル起動時にコンソール(コマンドプロンプト)を表示しない

–icon:アイコンファイルのパスを指定する

mv dist/main.exe main.exe

exeファイルの場所を移動する。

④specファイルの修正(main.specの修正)

作成されたspecファイル内のexe=EXE()に Tree('./icon’, prefix=’icon_path’) を追加する。

exe = EXE(pyz,
          Tree('./icon', prefix='icon_path'), # <- 追加
          a.scripts,
          b.binaries
          a.zipfiles,
          a.datas,
          [],
          name='main',
          debug=False,
          bootloades_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=Flase,
          icon='icon\\icon.ico')

⑤Pythonスクリプト(main.py)の修正

Pythonスクリプト(main.py)を修正して、実行ファイル起動時のウィンドウにもアイコンが埋め込まれるようにする。

・リソースフォルダにアクセスする関数 resourcePath を作成(コピペでOK)

def resourcePath(filename):
   if hasattr(sys, "_MEIPASS"):
       return os.path.join(sys._MEIPASS, filename) 
   return os.path.join(filename)

・if __name__ == “__main__": 以降に下記コードを追加(ファイル名"icon”は適宜修正)※ linuxだとico拡張子でエラー出るのでpng作成して埋め込む

filename = resourcePath( 'icon_path/icon.ico') 
img = Image.open(filename)
img.save(os.path.splitext(filename)[0]+".png")
root.tk.call('wm', 'iconphoto', root._W,
tk.PhotoImage(file=resourcePath('icon_path/icon.png')))

コード全体は以下のように書き換わる。

# main.py
import tkinter as tk
from PIL import ImageTk
import sys 
import os

def resourcePath(filename):
   if hasattr(sys, "_MEIPASS"):
       return os.path.join(sys._MEIPASS, filename) 
   return os.path.join(filename)


class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("画像の表示")       # ウィンドウタイトル
        self.master.geometry("500x500")     # ウィンドウサイズ(幅x高さ)

        # Canvasの作成
        self.canvas = tk.Canvas(self.master)
        # Canvasを配置
        self.canvas.pack(expand = True, fill = tk.BOTH)

        # 画像ファイルを開く
        self.photo_image = ImageTk.PhotoImage(file = "tanukis.png")

        # キャンバスのサイズを取得
        self.update() # Canvasのサイズを取得するため更新しておく
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()

        # 画像の描画
        self.canvas.create_image(
                canvas_width / 2,       # 画像表示位置(Canvasの中心)
                canvas_height / 2,                   
                image=self.photo_image  # 表示画像データ
                )

if __name__ == "__main__":
    filename = resourcePath( 'icon_path/icon.ico') 
    img = Image.open(filename)
    img.save(os.path.splitext(filename)[0]+".png")
    root.tk.call('wm', 'iconphoto', root._W,
    tk.PhotoImage(file=resourcePath('icon_path/icon.png')))
    
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

⑥実行ファイル"main.py"を再度作成

以下のコマンドを実行し、specファイルからdist/main.exeを作成して終了。

docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows -c \
  "pip install -r requirements.txt && \
  pyinstaller main.spec"

完成

作成したmain.exeを実行すると、

画像が表示されます。左上にはアイコン画像がついています。