コンテンツへスキップ

バージョン番号バンプの取りこぼし事故 — 単一ソース + –check ゲートで再発防止する設計

バージョン番号バンプの取りこぼし事故 — 単一ソース + --check ゲートで再発防止する設計

デスクトップアプリのリリースでは、バージョン番号を1つ上げるだけのつもりが、実は5箇所のファイルを同時に書き換える作業になっていることがある。ある回のリリースで、そのうち1箇所だけ更新を忘れ、古いバージョン番号のままインストーラーが生成されかけた。

補足: ここでの「バンプ」はバージョン番号を上げる作業のこと。1.6.91.6.10 にする、という単純な操作が、実際には複数ファイルへの分散更新になっている。

何が起きたか

このアプリのリリースでは、バージョン番号が次の5ファイルに分散して書かれている。

  • version.pyVERSION 変数
  • 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.pyVERSION を基準値として、他の全ファイルがそれと一致しているかを検証する。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 層の回帰ガード