はじめに
京セラコミュニケーションシステム株式会社 ICT事業本部 先端技術統括部の中橋(なかはし)です。
私たちの組織では、社内の開発プロセス改善を推進しており、特に業務効率化ツールの開発や、開発プロセスの自動化に取り組んでいます。今回はその取り組みの中で得た自動化の知見を、皆さんが一度は感じたことがあるであろう「あるある」を解決する方法としてご紹介します。
Pythonツール配布に潜む3つの課題
Pythonで作った便利なツールを他部署のメンバーに配布したい─。そんなとき、こんな課題に直面しませんか?
課題 | 解決策 | 方法 |
---|---|---|
実行環境の問題 例: 相手のPCにPythonがない。あってもバージョンが違って動かない… |
Python環境に依存しないツールを配布する | PyInstallerによるexe化 |
バージョン管理の問題 例: このexe、どの機能まで入れた最新版だっけ…? |
ファイルのプロパティから一目でバージョンを確認できるようにする | バージョン情報をexeに埋め込む |
属人化の問題 例: ビルド手順が複雑で、自分しかリリースできない… |
誰でも簡単にリリースできる仕組みを構築する | GitHub Actionsによる自動化 |
本記事では、これらの課題を解決し、誰でも安心して使え、継続的にメンテナンスできるPython製ツールの配布戦略を考え、具体的な手順を交えて紹介します。
本記事で学べること
- Pythonのパッケージ管理とexe化の基本
- バージョン情報をexeファイルへ反映させる方法
- GitHub Actionsによるビルド&リリースの自動化
想定読者
- Pythonでツールを作成し、チーム内外に配布したい方
- CI/CDによる自動化に興味がある方
- 属人化を解消し、チーム開発を改善したい方
環境構築・前提条件
環境
まず、開発環境については以下のような構成になっています。
項目 | バージョン |
---|---|
OS | Windows11 |
Python | 3.12.4 |
pyinstaller | 6.15.0 |
環境について
最終的にGitHub ActionsのWindowsランナーを使用するため、ローカル開発環境はWindowsでなくても問題ありません。ただし、Linux等で開発している場合、ローカルでの動作確認が一部できませんので、その点はご注意ください。
注意
開発環境構築時には、Pythonの仮想環境(venv
) を使用することをおすすめします。
プロジェクト構成
プロジェクトは以下のような構成で考えます。
project/ # プロジェクトのルートディレクトリ
├── mypackage # 自作パッケージ
│ ├── __init__.py
│ ├── __main__.py
│ ├── ...
│ └── subpackage # サブパッケージ
│ ├── __init__.py
│ ├── ...
├── tests/ # テストコード
│ └── ...
├── main.py # exe化の対象となるメインスクリプト
├── setup.py or pyproject.toml # パッケージ管理
├── VERSION.txt # バージョン情報
└── requirements.txt # 依存パッケージ一覧
この構成では、mypackage
として実装した機能を、main.py
から呼び出して実行する形式を取ります。setup.py
、pyproject.toml
は、パッケージ管理とメタデータ定義に使用します。
Pythonパッケージ管理の基礎知識
Pythonでは、プロジェクトの依存関係やメタデータを管理するためのさまざまなツールやファイルが存在します。ここでは、setup.py
、pyproject.toml
それぞれのパッケージ管理方法について紹介します。
バージョン管理
まず、VERSION.txt
には、以下のようにバージョン情報を記述します。
1.0.0.0
setup.py
を使用する方法
setup.py
は、プロジェクトのビルドやインストールを定義するPythonスクリプトです。以下のように、setuptools
ライブラリを使用して記述します。この例では、requirements.txt
とVERSION.txt
から依存関係とバージョン情報を読み込んでいます。
from setuptools import setup, find_packages
with open(file='requirements.txt', mode='r', encoding='utf-8') as require_file:
requires = require_file.read().splitlines()
with open(file='VERSION.txt', mode='r', encoding='utf-8') as vf:
version = vf.read().strip()
setup(
name='MyApp',
version=version,
description='自作アプリ',
packages=find_packages(exclude=['tests', 'tests.*']),
package_data={
'mypackage': ['*.*', '**/*.*']
},
include_package_data=True,
requires=requires
)
pyproject.toml
を使用する方法
pyproject.toml
は、PEP 518/621で導入された新しい標準的な設定ファイルです。ビルドシステムの要件やプロジェクトのメタデータを一元管理できるため、近年主流になりつつあります。ここでは、setuptools
を使用する設定例を紹介します。
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "MyApp"
description = "自作アプリ"
requires-python = ">=3.11"
dynamic = ["version", "dependencies"]
[tool.setuptools]
packages = ["mypackage"]
include-package-data = true
[tool.setuptools.package-data]
"project" = ["*.*", "**/*.*"]
[tool.setuptools.dynamic]
version = {file = "VERSION.txt"}
dependencies = {file = ["requirements.txt"]}
dependenciesについての補足
本記事では、既存のrequirements.txt
を活かして移行する方法を紹介しています。将来的には、依存関係をpyproject.toml
に直接記述して管理することが推奨されます。
[project]
name = "MyApp"
description = "自作アプリ"
requires-python = ">=3.11"
dependencies = ["requests"] # pyproject.tomlに記述する
dynamic = ["version"]
[tool.setuptools.dynamic]
version = {file = "VERSION.txt"}
自作パッケージのインストール方法
以下のコマンドで自作パッケージをローカルにインストールできます。
pip install .
PyInstallerでexe化とバージョン情報の埋め込み
このセクションでは、PythonスクリプトをWindows上で実行可能なexeファイルに変換するPyInstallerの使い方と、exeにバージョン情報を埋め込む方法について解説します。
基本
PyInstallerのインストール
まず、pip
を使ってPyInstallerをインストールします。
pip install pyinstaller
Pythonファイルをexe化する
例えば、main.py
というPythonファイルをexe化するには、以下のようなコマンドを実行します。
pyinstaller main.py
上記のコマンドを実行すると、dist
フォルダにmain.exe
というexeファイルが生成されます。しかし、このままでは関連ファイルが多数生成されたり、実行時にコンソールが表示されたりするため、より実用的なオプションを組み合わせるのが一般的です。
ここで、私がよく使うオプションについて簡単にまとめます。
オプション | 説明 | 用途 |
---|---|---|
--clean |
ビルド前に PyInstaller のキャッシュと一時ファイルを削除 | 古いビルドファイルやキャッシュが原因の不具合を防ぐために使用 |
-F , --onefile
|
関連ファイルを1つのファイルにまとめた実行ファイルを作成 | 単一の実行ファイルとして配布したい場合に使用 |
-n NAME , --name NAME
|
出力ファイル名を指定(デフォルト: 最初のスクリプトのベース名) | 出力ファイル名を任意に設定したい場合に使用 |
--collect-all MODULENAME |
指定したパッケージまたはモジュールの全リソースを収集する | 特定のパッケージの依存関係やリソースをすべて含めたい場合に使用 |
-w , --windowed , --noconsole
|
コンソール非表示で実行する | GUIアプリケーションを作成し、コンソールウィンドウを表示したくない場合に使用 |
--version-file FILE |
指定ファイルからバージョンリソースを埋め込む |
.exe のプロパティにバージョン/説明等を付与 |
上記のオプションを使用したより実用的な例です。mypackage
を--collect-all
で収集し、myapp.version
というファイルからバージョン情報を埋め込みます。
pyinstaller main.py --clean --onefile -n MyApp --collect-all mypackage --windowed --version-file myapp.version
exeへのバージョン埋め込み
PyInstallerで作成したexeファイルにバージョン情報を埋め込むと、ファイルのプロパティからバージョンを確認できるようになります。これにより、ユーザーが使っているバージョンを簡単に把握できます。
exeへバージョンを埋め込む場合は、先に述べた--version-file
オプションを使用します。しかし、バージョンファイルを手動で記述するのは手間がかかるため、サードパーティpyinstaller-versionfile
を使用します。
pyinstaller-versionfile
のインストール
pyinstaller-versionfile
をインストールするには、以下のいずれかのコマンドを実行します。
- PyPIからインストールする場合
pip install pyinstaller_versionfile
- GitHubリポジトリから直接インストールする場合
pip install git+https://github.com/DudeNr33/pyinstaller-versionfile.git@7e133fc9ffb999d0b4d265038520525f02154126
注意
※2025年8月5日時点においてPyPI経由のインストールでは特定のバグが修正されていないため、GitHubリポジトリからインストールすることをお勧めします。
バージョンファイルの作成
pyinstaller-versionfile
では、バージョンファイルを自動で生成できます。ここでは、ローカルにインストール済みのPythonパッケージのメタ情報からバージョンファイルを作成する方法を紹介します。
まず、setup.py
やpyproject.toml
を使って、作成した自作パッケージをローカルにインストールします。
pip install .
次に、以下のPythonスクリプト(ファイル名はcreate_version_file.py
とします)を実行します。これにより、Pythonパッケージのメタデータからバージョン情報が自動生成され、myapp.version
というファイルが作成されます。
# create_version_file.py
from pyinstaller_versionfile import create_versionfile_from_distribution
create_versionfile_from_distribution(
output_file='myapp.version',
distname='MyApp'
)
もし、自動生成された情報の一部を上書きしたい場合は、create_versionfile_from_distribution()
の引数に辞書形式で値を渡すことで、内容をカスタマイズできます。
data: dict[str, str | list[int]] = {
'company_name': '京セラコミュニケーションシステム株式会社', # 会社名
'legal_copyright': '© Kyocera Communication Systems Co., Ltd All rights reserved.', # コピーライト
'original_filename': 'MyApp.exe', # ファイル名
'product_name': 'MyProduct', # プロダクト名
'translations': [0x0411, 1200] # 言語設定
}
create_versionfile_from_distribution(
output_file='myapp.version',
distname='MyApp',
**data
)
生成されたバージョンファイル(myapp.version
)
# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0. Must always contain 4 elements.
filevers=(1,0,0,0),
prodvers=(1,0,0,0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
flags=0x0,
# The operating system for which this file was designed.
# 0x4 - NT and there is no need to change it.
OS=0x40004,
# The general type of file.
# 0x1 - the file is an application.
fileType=0x1,
# The function of the file.
# 0x0 - the function is not defined for this fileType
subtype=0x0,
# Creation date and time stamp.
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u'京セラコミュニケーションシステム株式会社'),
StringStruct(u'FileDescription', u'自作アプリ'),
StringStruct(u'FileVersion', u'1.0.0.0'),
StringStruct(u'InternalName', u'MyApp'),
StringStruct(u'LegalCopyright', u'© Kyocera Communication Systems Co., Ltd All rights reserved.'),
StringStruct(u'OriginalFilename', u'MyApp.exe'),
StringStruct(u'ProductName', u'MyProduct'),
StringStruct(u'ProductVersion', u'1.0.0.0')])
]),
VarFileInfo([VarStruct(u'Translation', [1041, 1200])])
]
)
このmyapp.version
ファイルを、pyinstaller
コマンドの--version-file
オプションで指定することで、exeにバージョン情報を埋め込むことができます。
言語設定
translationsの値については、pyinstaller-versionfile
のドキュメントおよびMSのドキュメントをご参照ください。本記事では、日本語(0x0411)とUnicode(1200)を指定しています。
パッケージ管理とexeバージョン埋め込みの連携
個人的に、設定値をスクリプト内に直接記述(ハードコーディング)するのは避けたいと考えています。そのため、これらの設定値を外部ファイルで管理する方法をいくつか紹介します。
例えば、以下のようにversion_info.yml
というYAMLファイルを作成します。
company_name: 京セラコミュニケーションシステム株式会社
legal_copyright: © Kyocera Communication Systems Co., Ltd All rights reserved.
original_filename: MyApp.exe
product_name: MyProduct
translations:
- 0x0411
- 1200
このYAMLファイルを読み込むようにcreate_version_file.py
を以下のように修正します。
import yaml
from pyinstaller_versionfile import create_versionfile_from_distribution
# 直接記述せず、設定ファイルから読み込む
with open(file='version_info.yml', mode='r', encoding='utf-8') as f:
version_info: dict[str, str | list[int]] = yaml.safe_load(f)
create_versionfile_from_distribution(
output_file='myapp.version',
distname='MyApp',
**version_info
)
パッケージ管理において、pyproject.toml
を使用している場合は、設定をこのファイルに統合することで、バージョン情報を一元管理できます。
# pyinstaller用のカスタムメタデータ
[tool.pyinstaller-versionfile]
company_name = "京セラコミュニケーションシステム株式会社"
legal_copyright = "© Kyocera Communication Systems Co., Ltd All rights reserved."
original_filename = "MyApp.exe"
product_name = "MyProduct"
translations = [
0x0411, 1200
]
この場合、create_version_file.py
はpyproject.toml
を読み込むように、以下のように修正します。
import tomllib
from pyinstaller_versionfile import create_versionfile_from_distribution
with open('pyproject.toml', 'rb') as f:
pyproject_data = tomllib.load(f)
version_info: dict[str, str | list[int]] = pyproject_data.get('tool', {}).get('pyinstaller-versionfile', {})
create_versionfile_from_distribution(
output_file='myapp.version',
distname='MyApp',
**version_info
)
GitHub Actionsでビルドとリリースを自動化
これまでの手順をGitHub Actionsを使って自動化することで、誰でも簡単にツールをリリースできる仕組みを構築します。
ワークフロー設計
ワークフローは、以下のようなシナリオで設計します。
- 開発者が
main
ブランチに対してPull Requestを作成・マージする。 -
main
ブランチへのマージをトリガーに、GitHub Actionsが自動で起動する。 - GitHub Actionsが、
pyinstaller
を使ってexeファイルをビルドする。 - ビルドされたexeファイルを、GitHubのリリースページにアップロードする。
このワークフローを構築することで、開発者はコードをマージするだけでリリースが完了し、リリース作業の属人化を防ぐことができます。
ワークフローの構築
それでは、このワークフローを具体的に構築していきます。
まず、プロジェクトルート直下に.github/workflows
ディレクトリを作成し、その中にrelease.yml
ファイルを作成します。
1. ワークフロー名とトリガーの設定
最初に、ワークフローの名前と、ワークフローを実行するタイミング(トリガー)を定義します。ここでは、main
ブランチへのプルリクエストがマージされたときに起動するように設定します。
name: Release # ワークフローの名前
on:
pull_request:
branches:
- "main" # 監視するブランチ
types:
- "closed" # プルリクエストが閉じられたときにトリガー
pull_request
イベントのclosed
タイプを使用することで、プルリクエストがマージされた後(またはクローズされた後)にのみ、ワークフローが実行されます。
2. ジョブ名・ランナーOS・権限の設定
次に、ワークフローの具体的な処理を定義するジョブを作成します。
jobs:
release: # ジョブの名前
if: github.event.pull_request.merged == true # プルリクエストがマージされた場合のみ実行
runs-on: windows-latest # Windows環境で実行
permissions:
contents: "write" # リリース作成に必要な権限
pull-requests: "read" # Pull Requestの本文の読み取り権限
-
if
条件:github.event.pull_request.merged == true
とすることで、プルリクエストがマージされた場合のみジョブが実行されます。 -
runs-on
:windows-latest
を指定することで、最新のWindows環境でビルドを実行します。 -
permissions
: リリースを作成するためにcontents: write
権限が必要です。
3. ビルドからリリースノート作成までのステップ
ビルドからリリースまでの具体的なステップを定義します。
steps:
- name: Checkout Repository # リポジトリのチェックアウト
uses: actions/checkout@v4
- name: Set up Python # Python環境のセットアップ
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies # 必要なライブラリのインストール
run: |
python -m pip install --upgrade pip
pip install .
pip install pyinstaller==6.15.0
pip install git+https://github.com/DudeNr33/pyinstaller-versionfile.git@7e133fc9ffb999d0b4d265038520525f02154126
shell: pwsh
- name: Set VERSION environment variable from VERSION.txt # バージョン情報の環境変数へのセット
run: |
if (-not (Test-Path VERSION.txt)) {
Write-Error "VERSION.txt not found."
exit 1
}
$raw = Get-Content VERSION.txt -Raw
$version = $raw.Trim()
echo "VERSION=$version" >> $env:GITHUB_ENV
shell: pwsh
- name: Display MyApp version
run: |
Write-Host "MyApp VERSION: ${{ env.VERSION }}"
shell: pwsh
- name: Create version file # PyInstaller用のバージョンファイルを作成
run: |
python create_version_file.py
shell: pwsh
- name: Build with PyInstaller # PyInstallerでexeをビルド
run: |
pyinstaller main.py --clean --onefile -n MyApp --collect-all mypackage --windowed --version-file myapp.version
shell: pwsh
- name: Create Release with gh CLI # ビルドされたexeファイルをリリースに添付
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v${{ env.VERSION }} .\dist\MyApp.exe `
--title "Release v${{ env.VERSION }}" `
--notes "${{ github.event.pull_request.body }}" `
--target main
shell: pwsh
-
Checkout Repository:
actions/checkout@v4
を使って、リポジトリの最新のコードをチェックアウトします。 -
Set up Python:
actions/setup-python@v5
を使って、Python環境をセットアップします。 -
Install dependencies(※): PowerShell(
shell: pwsh
)を使って必要なライブラリをインストールします。ここではpip install .
で自作パッケージを、pip install pyinstaller
でPyInstallerをインストールしています。 -
Set VERSION environment variable:
VERSION.txt
からバージョン情報を読み込み、GitHub Actionsの環境変数VERSION
に設定します。このバージョンは、リリース名やタグとして使用します。 -
Create version file: 前述の
pyinstaller-versionfile
を使って、exeに埋め込むためのバージョンファイルを生成します。 -
Build with PyInstaller:
pyinstaller
コマンドを実行して、exeファイルをビルドします。--version-file
オプションで先ほど作成したバージョンファイルを指定しています。 -
Create Release with gh CLI:
gh
コマンド(GitHub CLI) を使用して、GitHubのリリースを作成します。-
gh release create
: リリースを作成するコマンドです。 -
gh release create ${{ env.VERSION }} ".\dist\MyApp.exe"
: タグ名と添付するファイル(ビルドしたexe)を指定します。 -
--title "Release ${{ env.VERSION }}"
: リリースのタイトルをバージョン情報から生成します。 -
--notes "${{ github.event.pull_request.body }}"
: Pull Requestの本文をリリースノートに自動で記載します。これにより、リリースごとの変更点を自動的に記載できます。 -
--target main
: リリースが紐づくブランチを指定します。
-
このワークフローを実行することで、main
ブランチへマージされるたびに、自動的にビルドとリリースが行われるようになります。
※補足
上記ワークフローで使用している pyinstaller
および pyinstaller-versionfile
は、アプリ実行時の依存関係ではなくビルド用ライブラリです。そのため、通常の依存関係(requirements.txt
)には含めず、個別にインストールしています。
なお、依存関係をより厳密に管理したい場合には、実行用とビルド用を分離する方法があります(例:requirements-dev.txt、extra_requires/optional-dependencies、pip install .[dev]
)。
ただしこれは、本記事の主題からは外れるため、ここでは詳細を扱いません。
参考サイト
以下、本記事を書くにあたって参考にしたサイトです。
- https://voltaney.hateblo.jp/entry/2023/06/24/063550
- https://qiita.com/studio_haneya/items/9aad8f9ede11e58b41a8
- https://setuptools.pypa.io/en/latest/userguide/quickstart.html
- https://packaging.python.org/ja/latest/guides/writing-pyproject-toml/
- https://zenn.dev/hoshi_ophiuchus/scraps/8c147e4cb961d8
おわりに
本記事では、GitHub Actionsを用いたPythonプログラムのリリース自動化について解説しました。
ここで重要なのはPythonでも実行ファイルにバージョンを付与する方法でもありません。
私がこの取り組みで最も大切だと感じているのは、「誰でも、安心して、継続的に」という3つのキーワードです。
- 誰でも: 属人化をなくし、チームの誰もがツールをメンテナンスできること
- 安心して: バージョン情報が明確で、安定したツールを配布できること
- 継続的に: 開発・配布プロセスを自動化し、メンテナンスコストを下げて改善を続けられること
これらの観点を重視することで、チーム開発の質を向上させ、効率的で信頼性の高いソフトウェア開発が可能になると考えています。
本記事で紹介した方法が、皆さんのプロジェクトやチーム開発の一助となれば幸いです。
所属部署について
ITエンジニアが活躍する地方拠点「長崎 Innovation Lab」
長崎 Innovation Labでは、IT技術を活用して、工場などものづくりの現場で活用できるシステムの開発に取り組んでいます。自由な発想でこれまでにない社会に役立つ製品・サービスを生み出し、長崎から発信していくことを目指しています。