# -*- mode: python ; coding: utf-8 -*- """PyInstaller onefile 打包脚本。 /workspace/venvs/pkg/bin/pyinstaller filter-predict.spec 关键决定:**只内置 17MB 的判废兜底包,85MB 的类型包一律外置。** 实测代价:内置 102MB 模型 = 每次调用 +1.7s 解包 + /tmp 峰值 357MB(外置只有 163MB)。 所以文档第一行就必须写:首次部署要把 models/ 目录一起拷过去,只拷 exe 的话 --with-type 会以退出码 3 失败。 """ import os # spec 是被 exec 的普通 Python,【相对路径按调用 pyinstaller 时的 cwd 解析】, # 而 datas 元组里的相对源路径又是 PyInstaller 按 spec 所在目录解析的——两套规则。 # 所以这里一律用 spec 目录拼绝对路径:否则不在 release2/ 下敲 pyinstaller 就构建失败, # 更坏的是 cwd 下恰好有个同名目录时 os.listdir 会列出【那个目录】的文件名, # 产出一个能构建成功、运行时却退出码 5(模型文件不存在/大小不符)的 exe。 SPEC_DIR = globals().get('SPECPATH') or os.getcwd() BUILTIN = os.path.join(SPEC_DIR, 'models', 'reject-mnv3-gwz-320-v1') # 兜底包:与 v3 出厂同一权重 # 内置目录名必须【等于 pack.id】(程序会校验,防 rsync 改名); # 这样同 id 的外置包会自然把它遮蔽掉 —— 正是"拷一份新的到 exe 旁边就生效"想要的行为。 if not os.path.isdir(BUILTIN): raise SystemExit(f'✘ 找不到内置兜底包目录 {BUILTIN}') names = sorted(os.listdir(BUILTIN)) for need in ('model.onnx', 'pack.json'): if need not in names: raise SystemExit(f'✘ {BUILTIN} 里缺 {need},拒绝构建(打出来的 exe 会在运行时退出码 5)') datas = [] for fn in names: # .onnx.data(外置权重)必须一并打进去,否则运行时 ORT 直接 FAIL datas.append((os.path.join(BUILTIN, fn), 'models/' + os.path.basename(BUILTIN))) # ── 便携版:把 libstdc++.so.6 一起打进去 ──────────────────────────────── # 目标机 glibc 比构建机老时必需。v3 踩过这个坑:报错表象是 numpy 导入失败, # 根因却是 Ubuntu 24.04 的 libstdc++ 要 GLIBC_2.38 / GLIBCXX_3.4.33。 # FILTER_PORTABLE_LIBSTDCXX=/workspace/miniconda3/lib/libstdc++.so.6 pyinstaller filter-predict.spec # 注意:只打 libstdc++ 不够——libpython 本身也带 glibc 下限。真正决定下限的是 # 【构建用的 Python】,必须用 miniconda 那套(libpython 只要 GLIBC_2.28), # 而不是系统 python3.12(要 GLIBC_2.38)。构建后用 EXIT 前的自检行确认。 binaries = [] _libcxx = os.environ.get('FILTER_PORTABLE_LIBSTDCXX') if _libcxx: if not os.path.isfile(_libcxx): raise SystemExit(f'✘ FILTER_PORTABLE_LIBSTDCXX 指向的文件不存在: {_libcxx}') binaries.append((_libcxx, '.')) print(f'[spec] 便携模式:内嵌 {_libcxx}') a = Analysis( [os.path.join(SPEC_DIR, 'predict.py')], pathex=[SPEC_DIR], binaries=binaries, datas=datas, hiddenimports=['openpyxl'], # 可选依赖,装了就带上 excludes=['torch', 'timm', 'albumentations', 'cv2', 'scipy', 'pandas', 'matplotlib', 'sklearn', 'IPython'], noarchive=False, ) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, a.binaries, a.datas, [], name='filter-predict-v4', console=True, strip=False, upx=False, disable_windowed_traceback=False, argv_emulation=False)