ZB100ImageGenerator.spec 3.73 KB
# -*- mode: python ; coding: utf-8 -*-
"""
PyInstaller spec for ZB100ImageGenerator.

跨平台构建配置的唯一真相源 (macOS + Windows)。
build_mac_universal.sh / build_windows.bat 只负责环境准备,
实际构建调用 `pyinstaller ZB100ImageGenerator.spec`。

修复点:
- macOS 上 PIL/_imaging.so 依赖 @rpath/libtiff.6.dylib 等原生库,
  PyInstaller 把 @rpath 改写成 @loader_path/.. (= Contents/Frameworks/),
  因此 .dylibs/*.dylib 必须平铺到 bundle 根目录。

策略 (三重保险):
1. PyInstaller 官方工具 collect_dynamic_libs(destdir='.') —— 推荐做法
2. 显式枚举 PIL/.dylibs/ 和 PIL/.libs/ —— 兜底
3. 详细打印每一步收集结果 —— 下次出错直接看日志定位
"""
import sys
from pathlib import Path
from PyInstaller.utils.hooks import collect_dynamic_libs

IS_MAC = sys.platform == 'darwin'
IS_WIN = sys.platform == 'win32'

print('=' * 60)
print(f'[spec] 平台: {sys.platform}, Python: {sys.version.split()[0]}')

# ----- 图标 -----
if IS_MAC:
    ICON = 'zb100_mac.icns'
elif IS_WIN:
    ICON = 'zb100_windows.ico'
else:
    ICON = None

# ----- 数据文件 -----
datas = [('config.json', '.')]
if IS_WIN:
    datas.append(('zb100_windows.ico', '.'))

# ===== Pillow 原生库收集 =====

# 策略 1: PyInstaller 官方推荐
try:
    libs_std = collect_dynamic_libs('PIL', destdir='.')
    print(f'[spec] collect_dynamic_libs("PIL", destdir=".") 返回 {len(libs_std)} 项:')
    for src, dst in libs_std:
        print(f'[spec]   STD  {Path(src).name}  ->  {dst}')
except Exception as e:
    libs_std = []
    print(f'[spec] collect_dynamic_libs 异常: {e}')

# 策略 2: 显式枚举 PIL 包里的原生库目录
libs_extra = []
pil_dir = None
try:
    import PIL
    pil_dir = Path(PIL.__file__).parent
    print(f'[spec] PIL 安装位置: {pil_dir}')
    for sub in ('.dylibs', '.libs'):
        d = pil_dir / sub
        if d.is_dir():
            print(f'[spec] 发现 {sub}/ 目录, 内容:')
            for lib in sorted(d.iterdir()):
                print(f'[spec]   {lib.name}  ({lib.stat().st_size} bytes)')
                if lib.suffix.lower() in ('.dylib', '.so', '.dll'):
                    libs_extra.append((str(lib), '.'))
        else:
            print(f'[spec] {sub}/ 不存在')
except Exception as e:
    print(f'[spec] 枚举 PIL 失败: {e}')

# 合并去重 (按源路径)
seen = set()
pil_native_libs = []
for src, dst in libs_std + libs_extra:
    if src not in seen:
        seen.add(src)
        pil_native_libs.append((src, dst))

print(f'[spec] 最终 Pillow 原生库数量: {len(pil_native_libs)}')
for src, dst in pil_native_libs:
    print(f'[spec]   FINAL  {Path(src).name}  ->  {dst}')

if IS_MAC and len(pil_native_libs) == 0:
    print('[spec] !!! 警告: macOS 构建但未发现 Pillow 原生库, 打包产物必然启动失败 !!!')

print('=' * 60)

a = Analysis(
    ['image_generator.py'],
    pathex=[],
    binaries=pil_native_libs,
    datas=datas,
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='ZB100ImageGenerator',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon=[ICON] if ICON else None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='ZB100ImageGenerator',
)

if IS_MAC:
    app = BUNDLE(
        coll,
        name='ZB100ImageGenerator.app',
        icon=ICON,
        bundle_identifier=None,
    )