Files
WQ_GUI/water_quality_app.spec

225 lines
8.5 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.

# -*- coding: utf-8 -*-
# ==============================================================================
# 水质参数反演分析系统 - 终极纯净版 PyInstaller Spec 配置文件
# 彻底斩断 venv 交叉污染,强制 Conda 原生环境全量解析
# 用法: pyinstaller --clean water_quality_app.spec
# ==============================================================================
import os
import sys
import glob
import shutil
block_cipher = None
# ==============================================================================
# 0. 基础路径配置 (❌ 彻底移除了 VENV_SITE 变量,拒绝交叉环境污染!)
# ==============================================================================
PROJECT_ROOT = os.path.abspath(os.getcwd())
SRC_DIR = os.path.join(PROJECT_ROOT, "src")
BUILD_DIR = os.path.join(PROJECT_ROOT, "build")
DIST_DIR = os.path.join(PROJECT_ROOT, "dist")
# Conda WQ_GUI 环境 DLL 路径MSVC 运行时 + GIS 核心库原生纯净副本)
CONDA_ENV_DLLS = r"D:\111\changyongruanjian\anconda\envs\WQ_GUI"
CONDA_OSGEO_DLLS = os.path.join(CONDA_ENV_DLLS, "Lib", "site-packages", "osgeo")
# ==============================================================================
# 1. 静态资源与数据文件收集 (datas)
# ==============================================================================
def get_data_files():
datas = []
# 1-1. 根目录核心配置文件 (包含内置公式 CSV)
single_files = [
"waterindex.csv",
"requirements.txt",
"README.md",
"软件说明.md",
]
for f in single_files:
if os.path.exists(os.path.join(PROJECT_ROOT, f)):
datas.append((os.path.join(PROJECT_ROOT, f), "."))
# 1-2. data/ 目录(全量递归映射,确保代码通过 get_resource_path 能读到任务栏图标)
data_dir = os.path.join(PROJECT_ROOT, "data")
if os.path.isdir(data_dir):
datas.append((data_dir, "data"))
# 1-3. src/gui/model/ 模型参数文件
model_dir = os.path.join(SRC_DIR, "gui", "model")
if os.path.isdir(model_dir):
datas.append((model_dir, "src/gui/model"))
# 1-4. src/auth/ 离线授权验证模块
auth_dir = os.path.join(SRC_DIR, "auth")
if os.path.isdir(auth_dir):
datas.append((auth_dir, "src/auth"))
# 1-5. 运行时环境变量与 DLL 挂载钩子
rthook_script = os.path.join(PROJECT_ROOT, "scripts", "rthook_add_dll_dirs.py")
if os.path.exists(rthook_script):
datas.append((rthook_script, "scripts"))
return datas
datas = get_data_files()
# ==============================================================================
# 2. 核心二进制文件搜集 (binaries) - 强制抓取纯净运行库防静默崩溃
# ==============================================================================
def get_binaries():
binaries = []
# 2-1. 精准提取 Conda 环境下与编译完全匹配的 MSVC 运行库
if os.path.isdir(CONDA_ENV_DLLS):
for dll_name in [
"msvcp140.dll",
"msvcp140_1.dll",
"msvcp140_2.dll",
"msvcp140_atomic_wait.dll",
"msvcp140_codecvt_ids.dll",
"vcruntime140.dll",
"vcruntime140_1.dll",
"vcruntime140_threads.dll",
"concrt140.dll",
"vcomp140.dll",
"vcamp140.dll",
"vccorlib140.dll",
]:
dll_path = os.path.join(CONDA_ENV_DLLS, dll_name)
if os.path.exists(dll_path):
binaries.append((dll_path, "."))
# 2-2. 搜集底层 GDAL/GEOS/PROJ 核心框架与二进制扩展
if os.path.isdir(CONDA_OSGEO_DLLS):
for dll_name in ["gdal.dll", "geos.dll", "geos_c.dll", "proj_9.dll"]:
dll_path = os.path.join(CONDA_OSGEO_DLLS, dll_name)
if os.path.exists(dll_path):
binaries.append((dll_path, "."))
# 映射 Python 3.12 对应的底层二进制接口 (C-Extension)
for pyd_name in [
"_gdal.cp312-win_amd64.pyd",
"_gdal_array.cp312-win_amd64.pyd",
"_gdalconst.cp312-win_amd64.pyd"
]:
pyd_path = os.path.join(CONDA_OSGEO_DLLS, pyd_name)
if os.path.exists(pyd_path):
binaries.append((pyd_path, "osgeo"))
return binaries
binaries = get_binaries()
# ==============================================================================
# 3. 隐式依赖与底层动态加载池 (hiddenimports)
# ==============================================================================
hidden_imports = [
"PyQt5.sip",
# GDAL 空间处理核心
"osgeo", "osgeo.gdal", "osgeo.gdalconst", "osgeo.gdalnumeric", "osgeo.ogr", "osgeo.osr",
# 解决 rasterio 与 fiona 二进制隐藏扩展的断层报错
"rasterio._base", "rasterio._env", "rasterio._io", "rasterio._warp",
"rasterio._features", "rasterio._path", "rasterio.crs", "rasterio.env",
"rasterio.features", "rasterio.warp", "rasterio.windows", "rasterio.sample", "rasterio.vrt",
"fiona._env", "fiona._geometry", "fiona.ogrext", "fiona.io", "fiona.env", "fiona.features",
# 几何计算与坐标转换
"shapely._geos", "shapely._geometry", "shapely.lib",
"pyproj", "pyproj._datadir", "pyproj._crs",
"geopandas", "geopandas._compat", "geopandas.io",
# 底层科学计算库支持
"scipy.special._orthogonal", "scipy.spatial", "scipy.linalg",
"sklearn.utils._index_deprecations", "sklearn.utils._testing", "sklearn._loss",
"sklearn.ensemble", "sklearn.preprocessing", "sklearn.model_selection",
# 报告与文档导出组件
"docx", "docx.oxml", "docx.table", "docx.text",
"lxml.etree", "lxml.objectify",
# 业务面板与自定义挂载
"src.gui.styles", "src.gui.components.custom_widgets",
]
# ==============================================================================
# 4. 运行时前置初始化钩子 (runtime_hooks)
# ==============================================================================
runtime_hooks = []
rthook_path = os.path.join(PROJECT_ROOT, "scripts", "rthook_add_dll_dirs.py")
if os.path.exists(rthook_path):
runtime_hooks.append(rthook_path)
# ==============================================================================
# 5. 精简排除列表 (excludes) - 严格保留标准库防级联瘫痪
# ==============================================================================
excludes = [
"tkinter",
"matplotlib.tests",
"PyQt5.QtTest",
"PyQt5.QtWebEngine",
"PyQt5.QtWebEngineWidgets",
"pytest",
]
# ==============================================================================
# 6. 核心构建流程 (Analysis -> PYZ -> EXE -> COLLECT)
# ==============================================================================
appname = "MegaWater"
a = Analysis(
["src/gui/water_quality_gui_v2.py"],
# ⚠️ 绝对关键:仅保留项目原生搜索路径,彻底切断与混合虚拟环境的联系
pathex=[PROJECT_ROOT, SRC_DIR],
binaries=binaries,
datas=datas,
hiddenimports=hidden_imports,
hookspath=["scripts"] if os.path.isdir(os.path.join(PROJECT_ROOT, "scripts")) else [],
hooksconfig={},
runtime_hooks=runtime_hooks,
excludes=excludes,
win_no_prefer_redirects=False,
cipher=block_cipher,
noarchive=False,
)
# 强制清洗可能由于第三方包引入导致的、存在版本冲突的残缺 MSVC 动态库
a.binaries = [
x for x in a.binaries
if not x[0].lower().startswith(('msvcp140', 'vcruntime140'))
]
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
# 生成物理启动核心
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name=appname,
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False, # 禁用 UPX 暴力压缩,保护二进制 C++ 区段完整性
console=False, # ⚠️ 维持开启控制台,拒绝绝对静默崩溃
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon="data/icons-1/app.ico", # 锁定物理执行文件桌面正方形主图标
)
# 组装最终绿色分发目录 (_internal + EXE) - 引入全自动纯净 DLL 填坑机制
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
# 强制将原生编译的底层运行库塞入分发根目录,终结指针断层!
[
('msvcp140.dll', os.path.join(CONDA_ENV_DLLS, 'msvcp140.dll'), 'BINARY'),
('vcruntime140.dll', os.path.join(CONDA_ENV_DLLS, 'vcruntime140.dll'), 'BINARY')
],
strip=False,
upx=False,
upx_exclude=[],
name=appname,
)