#!/usr/bin/env python3 """ build_exe.py ─ mc_server_builder を exe にビルドする 1. exi-server.site から最新版を取得してインストール (--update) 2. PyInstaller でビルド 使い方: python build_exe.py # 現在の mc_server_builder.py をそのままビルド python build_exe.py --update # 最新版に更新してからビルド python build_exe.py --check # 更新チェックのみ(ビルドなし) """ import subprocess import sys import shutil import json import urllib.request import argparse from pathlib import Path # ── 定数 ───────────────────────────────────────── CURRENT_VERSION = "2.0" UPDATE_BASE_URL = "https://exi-server.site/mcserverbilder/" UPDATE_CHECK_URL = UPDATE_BASE_URL + "latestupdate" TARGET_SCRIPT = Path(__file__).parent / "mc_server_builder.py" EXE_NAME = "mc_server_builder" # ───────────────────────────────────────────────── # ユーティリティ # ───────────────────────────────────────────────── def _parse_version(v: str) -> tuple: try: return tuple(int(x) for x in str(v).strip().lstrip("v").split(".")) except Exception: return (0,) def _fetch(url: str, timeout: int = 30) -> bytes: req = urllib.request.Request( url, headers={"User-Agent": f"mc-network-builder/{CURRENT_VERSION}"} ) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read() def _section(title: str): print(f"\n{'─'*55}") print(f" {title}") print(f"{'─'*55}") # ───────────────────────────────────────────────── # 更新チェック / ダウンロード # ───────────────────────────────────────────────── def check_update() -> dict | None: """ サーバーから最新バージョン情報を取得する。 サーバーの応答フォーマット(どちらにも対応): JSON : {"version": "2.1", "url": "https://.../mc_server_builder.py"} テキスト: "2.1" (URLは BASE_URL + "mc_server_builder.py" を使用) """ _section("更新チェック") print(f" 現在のバージョン : v{CURRENT_VERSION}") print(f" 確認先 : {UPDATE_CHECK_URL}") try: raw = _fetch(UPDATE_CHECK_URL, timeout=10).decode("utf-8").strip() print(f" サーバー応答 : {raw[:120]}") try: data = json.loads(raw) latest_ver = str(data.get("version", "")).strip() dl_url = data.get("url", UPDATE_BASE_URL + "mc_server_builder.py") except json.JSONDecodeError: latest_ver = raw dl_url = UPDATE_BASE_URL + "mc_server_builder.py" if not latest_ver: print(" ⚠ バージョン情報を取得できませんでした") return None print(f" 最新バージョン : v{latest_ver}") if _parse_version(latest_ver) > _parse_version(CURRENT_VERSION): print(f" 🆕 新バージョンが利用可能です") return {"version": latest_ver, "url": dl_url} else: print(" ✅ 最新版です") return None except Exception as e: print(f" ❌ 更新チェック失敗: {e}") return None def download_latest(info: dict) -> bool: """最新の mc_server_builder.py をダウンロードして上書きする""" _section(f"v{info['version']} をダウンロード") url = info["url"] print(f" URL: {url}") backup = TARGET_SCRIPT.with_suffix(".py.bak") try: # バックアップ if TARGET_SCRIPT.exists(): shutil.copy2(TARGET_SCRIPT, backup) print(f" 💾 バックアップ: {backup}") # ダウンロード data = _fetch(url, timeout=60) TARGET_SCRIPT.write_bytes(data) size_kb = len(data) / 1024 print(f" ✅ ダウンロード完了: {TARGET_SCRIPT} ({size_kb:.1f} KB)") return True except Exception as e: print(f" ❌ ダウンロード失敗: {e}") if backup.exists(): shutil.copy2(backup, TARGET_SCRIPT) print(" ↩ バックアップから復元しました") return False # ───────────────────────────────────────────────── # PyInstaller ビルド # ───────────────────────────────────────────────── def find_pyinstaller() -> list: """pyinstaller の実行コマンドを返す""" # 1. python -m PyInstaller(最も確実) try: r = subprocess.run( [sys.executable, "-m", "PyInstaller", "--version"], capture_output=True, text=True ) if r.returncode == 0: print(f" PyInstaller {r.stdout.strip()} (python -m PyInstaller)") return [sys.executable, "-m", "PyInstaller"] except Exception: pass # 2. Scripts フォルダ scripts_dir = Path(sys.executable).parent / "Scripts" for name in ["pyinstaller.exe", "pyinstaller"]: p = scripts_dir / name if p.exists(): print(f" PyInstaller found: {p}") return [str(p)] # 3. PATH から探す w = shutil.which("pyinstaller") if w: return [w] return [] def build_exe() -> bool: """PyInstaller で exe をビルドする""" _section("PyInstaller ビルド") if not TARGET_SCRIPT.exists(): print(f" ❌ スクリプトが見つかりません: {TARGET_SCRIPT}") return False # PyInstaller が入っていなければ自動インストール print(" pip install pyinstaller ...") subprocess.run( [sys.executable, "-m", "pip", "install", "pyinstaller", "--quiet"], check=True ) pyi_cmd = find_pyinstaller() if not pyi_cmd: print(" ❌ PyInstaller が見つかりません") return False cmd = pyi_cmd + [ "--onefile", "--windowed", # Windows: コンソール非表示 (GUI モード) "--name", EXE_NAME, "--clean", # 前回のキャッシュを削除してクリーンビルド str(TARGET_SCRIPT), ] print(" コマンド:", " ".join(cmd)) result = subprocess.run(cmd) if result.returncode == 0: out = Path("dist") / (EXE_NAME + ".exe") if not out.exists(): out = Path("dist") / EXE_NAME if out.exists(): size_mb = out.stat().st_size / (1024 * 1024) print(f"\n ✅ ビルド成功: {out.resolve()} ({size_mb:.1f} MB)") else: print(f"\n ✅ ビルド成功 (dist/ フォルダを確認してください)") return True else: print("\n ❌ ビルド失敗") return False # ───────────────────────────────────────────────── # メイン # ───────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser( description="mc_server_builder のビルド & 更新スクリプト" ) parser.add_argument( "--update", action="store_true", help="最新版をダウンロードしてからビルドする" ) parser.add_argument( "--check", action="store_true", help="更新チェックのみ行い、ビルドはしない" ) args = parser.parse_args() print("=" * 55) print(" ⛏ Minecraft Network Manager ビルドスクリプト") print("=" * 55) # ── 更新チェック ────────────────────────────── if args.check: check_update() return if args.update: info = check_update() if info: ok = download_latest(info) if not ok: print("\n ❌ 更新に失敗しました。現在のファイルでビルドを続行します。") # 更新がなくても --update が指定されていればビルドまで進む # ── ビルド ──────────────────────────────────── ok = build_exe() sys.exit(0 if ok else 1) if __name__ == "__main__": main()