バージョン番号バンプの取りこぼし事故 — 単一ソース + --check ゲートで再発防止する設計
デスクトップアプリのリリースでは、バージョン番号を1つ上げるだけのつもりが、実は5箇所のファイルを同時に書き換える作業になっていることがある。ある回のリリースで、そのうち1箇所だけ更新を忘れ、古いバージョン番号のままインストーラーが生成されかけた。
補足: ここでの「バンプ」はバージョン番号を上げる作業のこと。
1.6.9を1.6.10にする、という単純な操作が、実際には複数ファイルへの分散更新になっている。
何が起きたか
このアプリのリリースでは、バージョン番号が次の5ファイルに分散して書かれている。
version.pyのVERSION変数- Windows インストーラー定義ファイル(
.iss)のMyAppVersion version.jsonのバージョン番号とダウンロードURL- LP(ランディングページ)のダウンロードリンク2箇所(日本語版・英語版)
リリース作業のたびに、これらを記憶を頼りに手作業で書き換えていた。ある回のリリースで、Windows ビルド環境側の .iss ファイルだけ旧バージョンのまま放置され、ビルドし直す段階になって発覚した。気づいたのは「あれ、まだ古いバージョン番号のままだ」という偶然の目視確認で、自動的な検出ではなかった。
なぜ記憶頼りの手作業が事故を誘発するか
5箇所という数自体は多くないが、「今回のリリースで触るべきファイルの一覧」は人の記憶に依存していた。一度成功しても、次のリリースでは同じ手順を再現する保証がない。さらに、ファイルごとに記法が違う(Python の変数代入・INI形式の定義・JSON・PHP内のリンク文字列)ため、横展開的に grep で確認するのも手間がかかる。
対処 — 対象ファイルを1箇所に列挙し、バンプと検証を同じスクリプトに統合する
tools/bump_version.py という単一のスクリプトに、バンプ対象ファイルとその正規表現パターンを列挙する。
VERSION_TARGETS = [
{"path": "version.py",
"checks": [{"regex": r'^VERSION\s*=\s*"([^"]+)"',
"replace": 'VERSION = "{version}"', "label": "VERSION"}]},
{"path": "WP_Maintenance_Pro.iss",
"checks": [{"regex": r'#define\s+MyAppVersion\s+"([^"]+)"',
"replace": '#define MyAppVersion "{version}"',
"label": "MyAppVersion"}]},
# ... version.json / index.php(日英)も同様に列挙
]
このスクリプトには2つのモードがある。
# 旧バージョンから新バージョンへ一括バンプ
python tools/bump_version.py 1.6.11 2026-06-11
# バンプ不要・現状の整合性だけを検証
python tools/bump_version.py --check
--check モードは、version.py の VERSION を基準値として、他の全ファイルがそれと一致しているかを検証する。1つでも不一致があれば、どのファイル・どの項目が・どんな値になっているかを具体的に出力し、終了コード1で失敗する。
def check_all(expected_version):
mismatches = []
for target in VERSION_TARGETS:
content = _read(os.path.join(PROJECT_ROOT, target["path"]))
for chk in target["checks"]:
m = re.search(chk["regex"], content)
actual = m.group(1) if m else "<not found>"
if actual != expected_version:
mismatches.append((target["path"], chk["label"], actual, expected_version))
return mismatches
ビルドスクリプトに事前ゲートとして組み込む
--check モードを単独で実行するだけでは、結局「実行を忘れる」という同じ問題が残る。そこで、アプリのビルドスクリプト自体に検証を組み込み、不整合があればビルドそのものを止める。
_check_result = subprocess.run(
[sys.executable, "tools/bump_version.py", "--check"],
capture_output=True, text=True,
)
if _check_result.returncode != 0:
print("❌ リリースファイルの版番号整合性チェックに失敗")
print(_check_result.stdout)
sys.exit(1)
これにより、ビルドを実行する人が --check の存在を覚えているかどうかに関係なく、署名・配布の前に必ず検証が走る。Mac・Windows どちらでビルドしても同じスクリプトが効くため、OS をまたいだ整合性確認にもなっている。
設計のポイント
この設計が解決しているのは「バージョン番号を書き換える作業」そのものではなく、「複数ファイルに分散した状態を、どこで・誰が・どう保証するか」という問題だ。対象ファイルの一覧を1つのスクリプトに集約することで、新しいリリース対象ファイルが増えても、追加すべき場所が明確になる。さらに「バンプする手段」と「ビルドを止める手段」を分離せず、同じスクリプトの2つのモードとして統合したことで、検証ロジックの二重実装も避けられている。
まとめ
| 観点 | 内容 |
|---|---|
| 問題 | バージョン番号が5ファイルに分散し、記憶頼りの手作業更新で取りこぼしが発生 |
| 対処 | 対象ファイルと正規表現パターンを1つのスクリプトに集約。バンプモードと検証モードを統合 |
| 検証方法 | version.py を基準値として他ファイルとの一致を正規表現で確認、不一致は具体的に報告 |
| 恒久化 | ビルドスクリプトに事前ゲートとして組み込み、実行忘れそのものを構造的に排除 |
複数ファイルにまたがる整合性は、一度手作業で揃えても次回また崩れる。検証を「実行する・しない」の選択に依存させず、ビルドという必ず通る経路に固定したことが、この設計の核だと思う。「ロジックを完璧にすることではなく、崩れた状態を機械的に検出して止める」という同じ考え方は、並び替えAPIの件数サニティチェックにも通じる。
続編: ビルド前ゲートの cp932 クラッシュ — Mac で露見しない Windows 文字コード地雷と 3 層の回帰ガード