Files
orc-order-v2/build_exe.py
T

335 lines
11 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
OCR订单处理系统 - EXE打包脚本
============================
自动化打包脚本,包含所有必要的资源文件和配置
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
def clean_build():
"""清理之前的构建文件"""
print("清理构建目录...")
dirs_to_clean = ['build', 'dist', '__pycache__']
for dir_name in dirs_to_clean:
if os.path.exists(dir_name):
shutil.rmtree(dir_name)
print(f"已删除: {dir_name}")
# 删除spec文件
spec_files = [f for f in os.listdir('.') if f.endswith('.spec')]
for spec_file in spec_files:
os.remove(spec_file)
print(f"已删除: {spec_file}")
def create_spec_file():
"""创建PyInstaller spec文件"""
spec_content = '''
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
# 需要包含的数据文件
added_files = [
('config.ini', '.'),
('config/barcode_mappings.json', 'config/'),
('config/config.ini', 'config/'),
('templates/银豹-采购单模板.xls', 'templates/'),
]
# 需要隐式导入的模块
hidden_imports = [
'tkinter',
'tkinter.ttk',
'tkinter.filedialog',
'tkinter.messagebox',
'tkinter.scrolledtext',
'pandas',
'numpy',
'openpyxl',
'xlrd',
'xlwt',
'xlutils',
'xlutils.copy',
'requests',
'dotenv',
'tkinterdnd2',
'configparser',
'threading',
'datetime',
'json',
're',
'subprocess',
'shutil',
'sqlite3',
'logging',
'base64',
'concurrent.futures',
'pathlib',
'typing',
]
a = Analysis(
['启动器.py'],
pathex=['.'],
binaries=[],
datas=added_files,
hiddenimports=hidden_imports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='OCR订单处理系统',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
'''
with open('OCR订单处理系统.spec', 'w', encoding='utf-8') as f:
f.write(spec_content)
print("已创建spec文件: OCR订单处理系统.spec")
def build_exe():
"""构建EXE文件"""
print("开始构建EXE文件...")
try:
# 注入版本信息到根config.ini
try:
root_cfg = Path('config.ini')
from datetime import datetime
version_str = datetime.now().strftime('%Y.%m.%d.%H%M')
if root_cfg.exists():
lines = root_cfg.read_text(encoding='utf-8').splitlines()
has_app = any(l.strip().lower() == '[app]' for l in lines)
if not has_app:
lines.append('[App]')
lines.append(f'version = {version_str}')
else:
# 更新或追加version
new_lines = []
in_app = False
app_written = False
for l in lines:
if l.strip().lower() == '[app]':
in_app = True
new_lines.append(l)
continue
if in_app and l.strip().lower().startswith('version'):
new_lines.append(f'version = {version_str}')
app_written = True
in_app = True
continue
new_lines.append(l)
if not app_written:
new_lines.append('version = ' + version_str)
lines = new_lines
root_cfg.write_text('\n'.join(lines), encoding='utf-8')
print(f"已写入版本号: {version_str}")
except Exception as e:
print(f"版本信息注入失败: {e}")
result = subprocess.run([
'pyinstaller',
'OCR订单处理系统.spec'
], check=True, capture_output=True, text=True)
print("构建成功!")
print(result.stdout)
# 构建完成后,复制完整的配置文件到dist目录
dist_dir = Path('dist')
# 复制包含API密钥的配置文件
config_file = Path('config/config.ini')
if config_file.exists():
# 确保config目录存在
(dist_dir / 'config').mkdir(exist_ok=True)
shutil.copy2(config_file, dist_dir / 'config')
print(f"已复制配置文件到dist: {config_file} -> {dist_dir / 'config'}")
# 复制完整的条码映射文件
barcode_mapping_file = Path('config/barcode_mappings.json')
if barcode_mapping_file.exists():
shutil.copy2(barcode_mapping_file, dist_dir / 'config')
print(f"已复制条码映射文件到dist: {barcode_mapping_file} -> {dist_dir / 'config'}")
# 复制根目录的config.ini文件(覆盖空的配置文件)
root_config_file = Path('config.ini')
if root_config_file.exists():
shutil.copy2(root_config_file, dist_dir)
print(f"已复制根配置文件到dist: {root_config_file} -> {dist_dir}")
else:
print("警告: 根配置文件不存在,将创建缺省版本")
(dist_dir / 'config.ini').write_text('[App]\nversion = dev\n', encoding='utf-8')
except subprocess.CalledProcessError as e:
print(f"构建失败: {e}")
print(f"错误输出: {e.stderr}")
return False
return True
def create_portable_package():
"""创建并更新便携版打包,并同步到桌面"""
print("更新便携版打包...")
# 1. 准备本地 release 目录
release_dir = Path('release')
# 不再删除整个目录,以保留 data/input 等用户数据
if not release_dir.exists():
release_dir.mkdir(parents=True)
print(f"已创建本地发布目录: {release_dir}")
else:
print(f"本地发布目录已存在,将进行增量更新: {release_dir}")
# 2. 更新核心文件到本地 release
# 复制exe文件
exe_file = Path('dist/OCR订单处理系统.exe')
if exe_file.exists():
shutil.copy2(exe_file, release_dir)
print(f"已更新 EXE: {exe_file} -> {release_dir}")
# 确保必要的子目录存在
dirs_to_ensure = ['data/input', 'data/output', 'data/result', 'logs', 'templates', 'config']
for dir_path in dirs_to_ensure:
(release_dir / dir_path).mkdir(parents=True, exist_ok=True)
# 复制配置文件
files_to_copy = [
(Path('config/config.ini'), release_dir / 'config'),
(Path('config/barcode_mappings.json'), release_dir / 'config'),
(Path('config.ini'), release_dir),
(Path('templates/银豹-采购单模板.xls'), release_dir / 'templates'),
(Path('templates/商品资料.xlsx'), release_dir / 'templates'),
]
for src, dst_dir in files_to_copy:
if src.exists():
dst_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst_dir)
print(f"已更新文件: {src} -> {dst_dir}")
else:
print(f"警告: 源文件不存在,跳过更新: {src}")
# 3. 创建/更新 README
readme_content = '''
# OCR订单处理系统 - 便携版
## 使用说明
1. 双击 "OCR订单处理系统.exe" 启动程序
2. 将需要处理的图片文件放入 data/input 目录
3. 处理结果将保存在 data/result 目录 (采购单 Excel)
4. 中间过程文件在 data/output 目录
5. 日志文件保存在 logs 目录
## 注意事项
- 支持的图片格式:jpg, jpeg, png, bmp
- 单个文件大小建议不超过 4MB
## 目录结构
- OCR订单处理系统.exe - 主程序
- data/input/ - 输入图片目录
- data/result/ - 最终采购单目录
- logs/ - 日志目录
'''
with open(release_dir / 'README.txt', 'w', encoding='utf-8') as f:
f.write(readme_content)
# 4. 同步到桌面
try:
# 用户指定的特殊桌面路径
desktop_path = Path(r"F:\Administrator\桌面")
if not desktop_path.exists():
# 兜底:如果 F 盘路径不存在,尝试系统默认路径
desktop_path = Path(os.path.join(os.path.expanduser("~"), "Desktop"))
desktop_release = desktop_path / "OCR系统_Release"
print(f"正在同步到桌面: {desktop_release}")
# 使用自定义的同步逻辑,避免删除目标目录中的其他文件
def sync_dir(src_root, dst_root):
if not dst_root.exists():
dst_root.mkdir(parents=True)
for item in src_root.iterdir():
dst_item = dst_root / item.name
if item.is_dir():
sync_dir(item, dst_item)
else:
# 如果是文件,直接覆盖更新
shutil.copy2(item, dst_item)
sync_dir(release_dir, desktop_release)
print(f"同步成功!桌面位置: {desktop_release.absolute()}")
except Exception as e:
print(f"同步到桌面失败: {e}")
print(f"本地便携版更新完成,位置: {release_dir.absolute()}")
def main():
"""主函数"""
print("=" * 50)
print("OCR订单处理系统 - EXE打包工具")
print("=" * 50)
# 检查是否安装了PyInstaller
try:
subprocess.run(['pyinstaller', '--version'], check=True, capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("错误: 未安装PyInstaller")
print("请运行: pip install pyinstaller")
return 1
# 清理构建目录
clean_build()
# 创建spec文件
create_spec_file()
# 构建EXE
if not build_exe():
return 1
# 创建便携版打包
create_portable_package()
print("\n" + "=" * 50)
print("打包完成!")
print("EXE文件位置: dist/OCR订单处理系统.exe")
print("便携版位置: release/")
print("=" * 50)
return 0
if __name__ == '__main__':
sys.exit(main())