feat: 增加 Client Windows 打包基础 (#92)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Client Windows 打包辅助代码。"""
|
||||
@@ -0,0 +1,53 @@
|
||||
"""发布包的轻量启动器。
|
||||
|
||||
启动器不联网、不更新文件,只负责从发布根目录启动 ``app/CMAutoBuy.exe``。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def install_root(executable: str | None = None) -> Path:
|
||||
"""返回 Launcher.exe 所在的发布根目录。"""
|
||||
|
||||
return Path(executable or sys.executable).resolve().parent
|
||||
|
||||
|
||||
def app_executable(root: Path) -> Path:
|
||||
"""返回主程序路径。"""
|
||||
|
||||
return root / "app" / "CMAutoBuy.exe"
|
||||
|
||||
|
||||
def show_error(message: str) -> None:
|
||||
"""使用 Windows 原生对话框显示启动错误。"""
|
||||
|
||||
ctypes.windll.user32.MessageBoxW(0, message, "商品采集采购工具", 0x10)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""启动主程序;找不到程序时返回非零退出码。"""
|
||||
|
||||
root = install_root()
|
||||
target = app_executable(root)
|
||||
if not target.is_file():
|
||||
show_error(
|
||||
"主程序不存在,请确认 app 文件夹完整。\n\n"
|
||||
f"缺少文件:{target}"
|
||||
)
|
||||
return 1
|
||||
|
||||
try:
|
||||
subprocess.Popen([str(target)], cwd=str(target.parent))
|
||||
except OSError as exc:
|
||||
show_error(f"主程序启动失败。\n\n{exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,79 @@
|
||||
"""生成可校验的 Client 发布清单。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
MANIFEST_FILE_NAME = "autobuy——manifest.json"
|
||||
|
||||
|
||||
def file_description(path: Path) -> dict[str, Any]:
|
||||
"""返回发布文件的名称、字节大小和 SHA256。"""
|
||||
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as file:
|
||||
for block in iter(lambda: file.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return {
|
||||
"file": path.name,
|
||||
"size": path.stat().st_size,
|
||||
"sha256": digest.hexdigest(),
|
||||
}
|
||||
|
||||
|
||||
def build_manifest(version: str, update_zip: Path, portable_zip: Path) -> dict[str, Any]:
|
||||
"""组装发布清单数据。"""
|
||||
|
||||
if not version.strip():
|
||||
raise ValueError("版本号不能为空")
|
||||
for path in (update_zip, portable_zip):
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"product": "CMAutoBuy",
|
||||
"version": version,
|
||||
"update": file_description(update_zip),
|
||||
"portable": file_description(portable_zip),
|
||||
}
|
||||
|
||||
|
||||
def write_manifest(
|
||||
version: str,
|
||||
update_zip: Path,
|
||||
portable_zip: Path,
|
||||
output: Path,
|
||||
) -> None:
|
||||
"""把发布清单写成不带 BOM 的 UTF-8 JSON。"""
|
||||
|
||||
manifest = build_manifest(version, update_zip, portable_zip)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(
|
||||
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="生成 Client 发布清单")
|
||||
parser.add_argument("--version", required=True)
|
||||
parser.add_argument("--update", required=True, type=Path)
|
||||
parser.add_argument("--portable", required=True, type=Path)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
write_manifest(args.version, args.update, args.portable, args.output)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user