#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 组装 DAPLink Flash 发布包: 程序目录(exe + 内置 OpenOCD 工具链) → zip(解压即用) ================================================================ release/DAPLinkFlash/ 本身就是可直接使用的完整文件夹 (exe + _internal/ + toolchain/ + 说明), 再原样压成 zip —— 内容始终一致。 zip 解压后根目录为 DAPLinkFlash/, 与 exe 运行目录结构一致: DAPLinkFlash/ ├── DAPLinkFlash.exe 主程序(先运行 build_exe.py 打包) ├── _internal/ PyInstaller onedir 运行库 ├── toolchain/openocd/ 内置 OpenOCD(bin + scripts, GUI 自动定位) └── README.md 使用说明 OpenOCD 来源(--openocd-src > 环境变量 OPENOCD_SRC > 自动探测): 1. FlashTool/toolchain/openocd (随仓库自带, 若已 vendor) 2. D:/toolchain/xpack-openocd-0.11.0-3 3. D:/toolchain/openocd 整个工具链目录原样打包(不挑文件); scripts 布局自动兼容 xpack 0.11(scripts/) 与 0.12(openocd/scripts/)两种。 程序目录还会带一个空的 cfgs/ 目录(放自定义 OpenOCD cfg, 界面下拉可选)。 用法: python package_release.py 版本号自动取最近 git tag(DAPLinkFlash-v*) python package_release.py 0.1.0 手动指定版本号 python package_release.py 0.1.0 --openocd-src D:/toolchain/openocd 产物: release/DAPLinkFlash-v<版本>-win64.zip(zip 不入库, 上传到 Gitea Release) """ import argparse import os import shutil import subprocess import sys import zipfile SRC = os.path.dirname(os.path.abspath(__file__)) BASE = os.path.dirname(SRC) RELEASE = os.path.join(BASE, 'release') APP_NAME = 'DAPLinkFlash' APP_DIR = os.path.join(RELEASE, APP_NAME) # onedir 输出目录 = 解压后的程序文件夹 def detect_version(): """从最近的 DAPLinkFlash-v* tag 推断版本号, 无 tag 则报错提示手动指定""" try: out = subprocess.check_output( ['git', 'describe', '--tags', '--abbrev=0', '--match', 'DAPLinkFlash-v*'], cwd=BASE, stderr=subprocess.DEVNULL) return out.decode().strip().removeprefix(APP_NAME + '-v') except Exception: sys.exit('未能从 git tag 推断版本号, 请手动指定: ' 'python package_release.py <版本号>') def resolve_openocd_src(explicit): """定位 OpenOCD 工具链源目录(bin/openocd.exe 必在, scripts 布局兼容两种)""" candidates = [explicit, os.environ.get('OPENOCD_SRC'), os.path.join(BASE, 'toolchain', 'openocd'), r'D:/toolchain/xpack-openocd-0.11.0-3', r'D:/toolchain/openocd'] for cand in candidates: if not cand: continue if not os.path.isfile(os.path.join(cand, 'bin', 'openocd.exe')): continue scripts = os.path.join(cand, 'openocd', 'scripts') if not os.path.isdir(scripts): scripts = os.path.join(cand, 'scripts') if os.path.isdir(scripts): print(f'OpenOCD 工具链来源: {os.path.abspath(cand)}') return os.path.abspath(cand) sys.exit('未找到 OpenOCD 工具链(需 bin/openocd.exe + scripts/):\n' ' python package_release.py <版本> --openocd-src \n' ' 或设置环境变量 OPENOCD_SRC') def copy_toolchain(openocd_src): """整个工具链目录原样复制到程序目录 toolchain/openocd(不挑文件)""" dst_root = os.path.join(APP_DIR, 'toolchain', 'openocd') if os.path.isdir(dst_root): shutil.rmtree(dst_root) shutil.copytree(openocd_src, dst_root) return sum(len(fs) for _r, _d, fs in os.walk(dst_root)) CFGS_README = ( "把自定义 OpenOCD cfg 文件放到本目录(任意文件名.cfg)。\n" "DAPLink Flash 的 \"Target 配置\"/\"Interface 配置\" 下拉框会自动列出\n" "cfgs/<文件名>, 选中即用; 选择会被记住。\n" ) def ensure_cfgs_dir(): """程序目录建 cfgs/ + 说明文件""" d = os.path.join(APP_DIR, 'cfgs') os.makedirs(d, exist_ok=True) readme = os.path.join(d, '说明-把cfg放这里.txt') if not os.path.exists(readme): with open(readme, 'w', encoding='utf-8') as f: f.write(CFGS_README) return d def main(): ap = argparse.ArgumentParser() ap.add_argument('version', nargs='?', default=None, help='版本号, 默认取最近 DAPLinkFlash-v* git tag') ap.add_argument('--openocd-src', default=None, help='OpenOCD 工具链目录(含 bin/ 与 openocd/scripts/)') args = ap.parse_args() version = (args.version or detect_version()).lstrip('v') exe = os.path.join(APP_DIR, APP_NAME + '.exe') readme = os.path.join(BASE, 'README.md') if not os.path.exists(exe): sys.exit(f'缺少 {APP_DIR}/{APP_NAME}.exe, 先运行: python build_exe.py') if not os.path.exists(readme): sys.exit(f'缺少 {readme}') openocd_src = resolve_openocd_src(args.openocd_src) n_files = copy_toolchain(openocd_src) ensure_cfgs_dir() shutil.copy2(readme, os.path.join(APP_DIR, 'README.md')) # 用户自定义 cfg 目录(存在才打包): 界面 Target/Interface 下拉可选用 src_cfgs = os.path.join(BASE, 'cfgs') n_cfgs = 0 if os.path.isdir(src_cfgs): dst_cfgs = os.path.join(APP_DIR, 'cfgs') if os.path.isdir(dst_cfgs): shutil.rmtree(dst_cfgs) shutil.copytree(src_cfgs, dst_cfgs) n_cfgs = sum(len(fs) for _r, _d, fs in os.walk(dst_cfgs)) print(f'自定义 cfg: 复制 {n_cfgs} 个到发布包 cfgs/') zip_name = f'{APP_NAME}-v{version}-win64.zip' zip_path = os.path.join(RELEASE, zip_name) with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z: for root, _dirs, files in os.walk(APP_DIR): for f in files: if f == 'flash_gui_settings.json': # 本机运行配置不随包分发 continue full = os.path.join(root, f) z.write(full, os.path.join(APP_NAME, os.path.relpath(full, APP_DIR))) zip_mb = os.path.getsize(zip_path) / 1024 / 1024 print(f'发布文件夹: {APP_DIR} (exe + _internal + 内置工具链 {n_files} 个文件' + (f' + 自定义 cfg {n_cfgs} 个' if n_cfgs else '') + ', 平铺即用)') print(f'发布包: {zip_path} ({zip_mb:.1f} MB)') print('下一步上传: python publish_gitea.py', zip_name) if __name__ == '__main__': main()