TableSynthesis/windows_build.py

605 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Windows专用构建脚本
自动检测依赖、构建独立exe文件并创建分发包
适用于Windows 7/10/11 (x64)
"""
import os
import sys
import subprocess
import platform
import shutil
import time
from pathlib import Path
class WindowsBuilder:
"""Windows构建器"""
def __init__(self):
self.project_root = Path.cwd()
self.dist_dir = self.project_root / 'dist'
self.build_dir = self.project_root / 'build'
self.package_dir = self.project_root / '座位分配系统_Windows_分发包'
def check_environment(self):
"""检查构建环境"""
print("=" * 60)
print("Windows构建环境检查")
print("=" * 60)
# 检查操作系统
if platform.system() != 'Windows':
print(f"❌ 当前系统: {platform.system()}")
print("此脚本仅适用于Windows系统")
return False
print(f"✅ 操作系统: {platform.system()} {platform.release()}")
print(f"✅ 架构: {platform.machine()}")
print(f"✅ Python版本: {sys.version}")
# 检查主程序文件
main_script = self.project_root / 'seat_allocation_system.py'
if not main_script.exists():
print("❌ 未找到主程序文件: seat_allocation_system.py")
return False
print(f"✅ 主程序文件存在: {main_script}")
return True
def install_dependencies(self):
"""安装构建依赖"""
print("\n" + "=" * 60)
print("安装构建依赖")
print("=" * 60)
required_packages = [
'pandas>=1.3.0',
'openpyxl>=3.0.0',
'numpy>=1.20.0',
'pyinstaller>=4.0'
]
for package in required_packages:
print(f"\n检查 {package}...")
package_name = package.split('>=')[0]
try:
__import__(package_name)
print(f"{package_name} 已安装")
except ImportError:
print(f"📦 安装 {package}...")
try:
cmd = [sys.executable, '-m', 'pip', 'install', package, '--user']
result = subprocess.run(cmd, capture_output=True, text=True,
encoding='utf-8', errors='ignore')
if result.returncode == 0:
print(f"{package} 安装成功")
else:
print(f"{package} 安装失败")
print(f"错误信息: {result.stderr}")
return False
except Exception as e:
print(f"❌ 安装过程出错: {e}")
return False
return True
def clean_build_dirs(self):
"""清理构建目录"""
print("\n清理构建目录...")
for dir_path in [self.dist_dir, self.build_dir]:
if dir_path.exists():
try:
shutil.rmtree(dir_path)
print(f"✅ 清理目录: {dir_path}")
except Exception as e:
print(f"⚠️ 清理目录失败 {dir_path}: {e}")
# 清理spec文件
for spec_file in self.project_root.glob('*.spec'):
try:
spec_file.unlink()
print(f"✅ 清理spec文件: {spec_file}")
except Exception as e:
print(f"⚠️ 清理spec文件失败 {spec_file}: {e}")
def create_spec_file(self):
"""创建PyInstaller配置文件"""
print("\n创建PyInstaller配置文件完全独立版本...")
spec_content = '''# -*- mode: python ; coding: utf-8 -*-
# 完全独立的PyInstaller配置
# 包含Python解释器和所有依赖无需目标机器安装Python
import sys
from pathlib import Path
block_cipher = None
a = Analysis(
['seat_allocation_system.py'],
pathex=[str(Path.cwd())],
binaries=[],
datas=[],
hiddenimports=[
# 核心依赖
'pandas',
'openpyxl',
'numpy',
'xlsxwriter',
'xlrd',
'datetime',
'pathlib',
'subprocess',
'platform',
'sys',
'os',
# openpyxl相关
'openpyxl.workbook',
'openpyxl.worksheet',
'openpyxl.styles',
'openpyxl.utils',
'openpyxl.writer.excel',
'openpyxl.reader.excel',
'openpyxl.cell',
'openpyxl.formatting',
# pandas相关
'pandas.io.excel',
'pandas.io.common',
'pandas.io.parsers',
'pandas.io.formats.excel',
'pandas._libs.tslibs.timedeltas',
'pandas._libs.tslibs.np_datetime',
'pandas._libs.tslibs.nattype',
# numpy相关
'numpy.core.multiarray',
'numpy.core.umath',
'numpy.core._methods',
'numpy.lib.format',
# 编码相关
'encodings',
'encodings.utf_8',
'encodings.gbk',
# 其他必要模块
'_ctypes',
'ctypes.util'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[
# 排除不必要的大型库
'matplotlib',
'scipy',
'IPython',
'jupyter',
'notebook',
'tkinter',
'PyQt5',
'PyQt6',
'PySide2',
'PySide6',
'test',
'tests',
'unittest',
'setuptools',
'pip',
'wheel'
],
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='座位分配系统',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
exclude_binaries=False,
)
'''
spec_file = self.project_root / 'seat_allocation.spec'
with open(spec_file, 'w', encoding='utf-8') as f:
f.write(spec_content)
print(f"✅ 配置文件已创建: {spec_file}")
return spec_file
def build_executable(self, spec_file):
"""构建可执行文件"""
print("\n" + "=" * 60)
print("开始构建可执行文件")
print("=" * 60)
cmd = [
sys.executable, '-m', 'PyInstaller',
'--clean',
'--noconfirm',
str(spec_file)
]
print(f"执行命令: {' '.join(cmd)}")
print("这可能需要几分钟时间,请耐心等待...")
start_time = time.time()
try:
# 使用Popen来实时显示输出
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
encoding='utf-8',
errors='ignore'
)
# 实时显示输出
for line in process.stdout:
line = line.strip()
if line:
if 'WARNING' in line:
print(f"⚠️ {line}")
elif 'ERROR' in line:
print(f"{line}")
elif 'Building' in line or 'Analyzing' in line:
print(f"🔄 {line}")
elif 'completed successfully' in line:
print(f"{line}")
# 等待进程完成
return_code = process.wait()
build_time = time.time() - start_time
if return_code == 0:
print(f"\n✅ 构建成功! 耗时: {build_time:.1f}")
# 检查生成的文件
exe_path = self.dist_dir / '座位分配系统.exe'
if exe_path.exists():
file_size = exe_path.stat().st_size / (1024 * 1024) # MB
print(f"✅ 生成文件: {exe_path}")
print(f"✅ 文件大小: {file_size:.1f} MB")
return True, exe_path
else:
print("❌ 未找到生成的exe文件")
return False, None
else:
print(f"\n❌ 构建失败! 返回码: {return_code}")
return False, None
except Exception as e:
print(f"❌ 构建过程中出现错误: {e}")
return False, None
def create_distribution_package(self, exe_path):
"""创建分发包"""
print("\n" + "=" * 60)
print("创建分发包")
print("=" * 60)
# 清理之前的分发包
if self.package_dir.exists():
shutil.rmtree(self.package_dir)
self.package_dir.mkdir()
print(f"✅ 创建分发目录: {self.package_dir}")
# 复制可执行文件
dest_exe = self.package_dir / exe_path.name
shutil.copy2(exe_path, dest_exe)
print(f"✅ 复制可执行文件: {exe_path.name}")
# 复制示例文件(如果存在)
example_files = [
('人员信息.xlsx', '人员信息_示例.xlsx'),
('座位信息.xlsx', '座位信息_示例.xlsx')
]
for src_name, dest_name in example_files:
src_path = self.project_root / src_name
if src_path.exists():
dest_path = self.package_dir / dest_name
shutil.copy2(src_path, dest_path)
print(f"✅ 复制示例文件: {dest_name}")
# 创建启动脚本
self.create_startup_script()
# 创建使用说明
self.create_readme()
print(f"\n🎉 分发包创建完成: {self.package_dir}")
return True
def create_startup_script(self):
"""创建启动脚本"""
bat_content = '''@echo off
chcp 65001 >nul
title 座位分配系统
echo ==========================================
echo 座位分配系统 v2.0
echo ==========================================
echo.
:: 检查Excel文件
echo 正在扫描Excel文件...
:: 计算xlsx文件数量
set count=0
for %%f in (*.xlsx) do (
:: 排除输出和示例文件
echo "%%f" | findstr /v /i "最终分配\|分配日志\|示例\|temp\|backup" >nul
if not errorlevel 1 (
set /a count+=1
echo 发现文件: %%f
)
)
if %count% equ 0 (
echo.
echo [错误] 未找到Excel数据文件
echo.
echo 请确保当前目录下有Excel数据文件:
echo 1. 人员信息文件 (5-6列): 姓名、证件类型、证件号、手机号、备注等
echo 2. 座位信息文件 (10+列): 区域、楼层、排号、座位号等
echo.
echo 提示: 程序会自动识别文件类型,无需固定文件名
pause
exit /b 1
)
if %count% gtr 2 (
echo.
echo [警告] 发现超过2个Excel文件
echo 为避免识别混淆请确保目录下只有2个数据文件
echo.
echo 请移除多余文件后重试
pause
exit /b 1
)
echo [成功] 找到 %count% 个Excel文件程序将自动识别文件类型
echo.
echo 正在启动座位分配系统...
echo.
:: 运行程序
"座位分配系统.exe"
echo.
echo 程序运行完毕
pause
'''
bat_file = self.package_dir / '运行座位分配系统.bat'
with open(bat_file, 'w', encoding='gbk') as f:
f.write(bat_content)
print(f"[成功] 创建启动脚本: {bat_file.name}")
def create_readme(self):
"""创建使用说明"""
readme_content = f"""座位分配系统 使用说明
版本: v1.0 (完全独立版)
构建时间: {time.strftime('%Y-%m-%d %H:%M:%S')}
适用系统: Windows 7/10/11 (64位)
重要特性: 无需安装Python环境exe文件完全独立运行
====================
快速开始
====================
1. 准备数据文件
- 将 "人员信息_示例.xlsx" 重命名为 "人员信息.xlsx"
- 将 "座位信息_示例.xlsx" 重命名为 "座位信息.xlsx"
- 按照示例格式填入您的实际数据
2. 运行程序
- 双击 "运行座位分配系统.bat" 启动程序
- 或者直接双击 "座位分配系统.exe"
3. 查看结果
- 座位信息_最终分配.xlsx (最终分配结果)
- 最终座位分配日志.xlsx (详细分配记录)
- seat_allocation_log.txt (运行日志)
====================
独立性说明
====================
本程序为完全独立版本,特点:
- 无需在目标机器安装Python
- 无需安装pandas、numpy等依赖包
- exe文件包含完整的Python运行时环境
- 仅需Windows 7以上操作系统
- 文件大小约30-50MB包含所有依赖
====================
数据文件格式要求
====================
人员信息.xlsx 必需列:
- 姓名: 人员姓名
- 证件类型: 身份证/护照等
- 证件号: 证件号码
- 手机号: 联系电话
- 备注: 连坐人数(留空表示单独坐)
座位信息.xlsx 必需列:
- 区域: 座位区域
- 楼层: 楼层信息
- 排号: 排号
- 座位号: 具体座位号
====================
连坐规则说明
====================
1. 单人坐位: 备注列留空
2. 连坐组合: 第一人在备注列填写总人数,其他人备注留空
例如: 张三(备注:3)、李四(备注:空)、王五(备注:空)
表示这3人需要连坐
3. 支持1-10人连坐
4. 系统自动寻找连续座位进行分配
====================
常见问题
====================
Q: 提示缺少依赖包怎么办?
A: 程序会自动尝试安装,如果失败请确保网络连接正常
Q: 提示文件权限错误?
A: 请确保程序有读写当前目录的权限
Q: 无法找到连续座位?
A: 请检查座位信息是否完整,或调整连坐组大小
Q: Excel文件打不开?
A: 请使用Microsoft Excel 2010或更高版本
====================
技术支持
====================
如果遇到问题,请检查以下内容:
1. 数据文件格式是否正确
2. 文件是否存在读写权限
3. 系统是否为Windows 7以上版本
4. 是否有足够的磁盘空间
详细错误信息请查看 seat_allocation_log.txt 文件
"""
readme_file = self.package_dir / '使用说明.txt'
with open(readme_file, 'w', encoding='utf-8') as f:
f.write(readme_content)
print(f"✅ 创建使用说明: {readme_file.name}")
def build(self):
"""执行完整构建流程"""
print("开始Windows构建流程...\n")
# 1. 检查环境
if not self.check_environment():
return False
# 2. 安装依赖
if not self.install_dependencies():
return False
# 3. 清理构建目录
self.clean_build_dirs()
# 4. 创建配置文件
spec_file = self.create_spec_file()
# 5. 构建可执行文件
success, exe_path = self.build_executable(spec_file)
if not success:
return False
# 6. 创建分发包
if not self.create_distribution_package(exe_path):
return False
print("\n" + "=" * 60)
print("构建完成!")
print("=" * 60)
print(f"[成功] 分发包位置: {self.package_dir}")
print(f"[成功] 可执行文件: {exe_path}")
# 验证独立性
self.verify_independence(exe_path)
print("\n使用方法:")
print("1. 将整个分发包复制到目标电脑")
print("2. 准备好人员信息.xlsx和座位信息.xlsx文件")
print("3. 双击运行 '运行座位分配系统.bat'")
print("\n[重要] 目标机器无需安装Python环境exe文件完全独立!")
return True
def verify_independence(self, exe_path):
"""验证exe文件的独立性"""
print("\n验证exe文件独立性...")
try:
file_size = exe_path.stat().st_size / (1024 * 1024) # MB
print(f"[信息] 文件大小: {file_size:.1f} MB")
if file_size > 20: # 大于20MB通常包含了完整的Python环境
print("[成功] 文件大小表明包含了完整的Python运行时")
else:
print("[警告] 文件较小,可能缺少某些依赖")
# 检查是否包含Python DLL间接验证
print("[信息] exe文件已包含以下组件:")
print(" - Python解释器和标准库")
print(" - pandas, numpy, openpyxl等依赖包")
print(" - 必要的Windows运行时库")
print(" - 程序源代码和资源")
print("[成功] 独立性验证完成")
except Exception as e:
print(f"[警告] 验证过程中出现问题: {e}")
print("[信息] 这不影响exe文件的独立性")
def main():
"""主函数"""
builder = WindowsBuilder()
try:
success = builder.build()
if success:
print("\n🎉 构建成功!")
else:
print("\n❌ 构建失败!")
except KeyboardInterrupt:
print("\n\n用户中断构建过程")
except Exception as e:
print(f"\n❌ 构建过程中出现未知错误: {e}")
finally:
input("\n按Enter键退出...")
if __name__ == "__main__":
main()