Python を用いてGUIアプリケーションの配布用実行ファイルを作る

#はじめに
Python を利用して実行ファイルを作る記事は様々ありましたが，難読化や利用したライブラリをまとめるなどの操作がまとまった記事はなかなか見つからなかったので書いておきます．

#やりたいこと
Python で書いたプログラムを実行ファイルにし，難読化を行って配布する．

#環境
python = 3.8

#パッケージ
pip install pyinstaller==4.3（記事執筆時の最新版は4.8）
pip install pyarmor
pip install pip-licenses

#サンプルコード
下記リンクのコードを元にしています．
https://qiita.com/seisantaro/items/74ed83fec3d126553245
https://qiita.com/y-tsutsu/items/a8cc1578dd2f930e5439

実行ファイルにアイコン画像を設定する際，エクスプローラなどの上で表示されるアイコン設定と，タスクバーとウィンドウに表示されるアイコンは別々に指定する必要があることに注意が必要です．
前者は実行ファイル生成時にオプションをつけるのみで良いですが，後者は実行ファイル生成時にファイルをバンドルし，プログラム内でも設定すると記述する必要があります．

```main.py
# -*- coding: utf-8 -*-
import tkinter as tk
import os, sys

#アイコン画像取得用の関数
def resource_path(relative):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative)
    return relative

class App(tk.Tk):
    # 呪文
    def __init__(self, *args, **kwargs):
        # 呪文
        tk.Tk.__init__(self, *args, **kwargs)

        # ウィンドウタイトルを決定
        self.title("Tkinter change page")

        # ウィンドウの大きさを決定
        self.geometry("800x600")

        # ウインドウのサイズ変更可否指定
        self.resizable(width=False, height=False)

        # タイトルバーやタスクバーのアイコンを設定
        self.iconbitmap(resource_path("icon.ico"))

        # ウィンドウのグリッドを 1x1 にする
        # この処理をコメントアウトすると配置がズレる
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
#-----------------------------------main_frame-----------------------------
        # メインページフレーム作成
        self.main_frame = tk.Frame()
        self.main_frame.grid(row=0, column=0, sticky="nsew")
        # タイトルラベル作成
        self.titleLabel = tk.Label(self.main_frame, text="Main Page", font=('Helvetica', '35'))
        self.titleLabel.pack(anchor='center', expand=True)
        # フレーム1に移動するボタン
        self.changePageButton = tk.Button(self.main_frame, text="Go to frame1", command=lambda : self.changePage(self.frame1))
        self.changePageButton.pack()
#--------------------------------------------------------------------------
#-----------------------------------frame1---------------------------------
        # 移動先フレーム作成
        self.frame1 = tk.Frame()
        self.frame1.grid(row=0, column=0, sticky="nsew")
        # タイトルラベル作成
        self.titleLabel = tk.Label(self.frame1, text="Frame 1", font=('Helvetica', '35'))
        self.titleLabel.pack(anchor='center', expand=True)
        # フレーム1からmainフレームに戻るボタン
        self.back_button = tk.Button(self.frame1, text="Back", command=lambda : self.changePage(self.main_frame))
        self.back_button.pack()
#--------------------------------------------------------------------------

        #main_frameを一番上に表示
        self.main_frame.tkraise()

    def changePage(self, page):
        '''
        画面遷移用の関数
        '''
        page.tkraise()

if __name__ == "__main__":
    app = App()
    app.mainloop()
```

#実行ファイル生成
コンソールでコードを置いたディレクトリまで移動し，下記のコマンドを入力します．

```
# ライセンス期限の設定，今回は 2022/3/31
pyarmor licenses --expired 2022-03-31 code
# pack で実行ファイル生成，-with-license のオプションで期限の設定，-e のオプションで pyinstaller のオプション設定
pyarmor pack --with-license licenses/code/license.lic -e " --onefile --noconsole --add-data .\\icon.ico;. --icon icon.ico" main.py
```

pyinstaller のオプションを見ていきます．--onefile のオプションで実行ファイルを１つにまとめています．--noconsole のオプションでコンソールを非表示にしています．--add-data のオプションでアイコンファイルをバンドルしています．--icon のオプションで実行ファイルのアイコン画像を設定しています．

#ライセンスファイル生成
配布時は利用したライブラリのライセンスをまとめておく必要があるので，コンソールに下記の記述を行うことでファイルにまとめることができます．

```
# 利用したライブラリとライセンスをまとめてファイル出力
pip-licenses --format=rst --output-file=LICENSE.rst
```


#参考
https://www.delftstack.com/ja/howto/python-tkinter/how-to-set-window-icon-in-tkinter/
https://qiita.com/y-tsutsu/items/a8cc1578dd2f930e5439
https://qiita.com/Dr_Thomas/items/f3b2eff79cba10eb42a3
https://lets-hack.tech/programming/languages/python/pyinstaller/
https://qiita.com/seisantaro/items/74ed83fec3d126553245
https://pyarmor.readthedocs.io/en/latest/man.html?highlight=pack#pack
https://pypi.org/project/pip-licenses/