diff --git a/data/icons-1/app.ico b/data/icons-1/app.ico new file mode 100644 index 0000000..a5cc4a7 Binary files /dev/null and b/data/icons-1/app.ico differ diff --git a/src/gui/water_quality_gui_v2.py b/src/gui/water_quality_gui_v2.py index dae12e7..e602d62 100644 --- a/src/gui/water_quality_gui_v2.py +++ b/src/gui/water_quality_gui_v2.py @@ -336,7 +336,7 @@ class WaterQualityGUI(QMainWindow): ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(my_appid) super().__init__() - icon_path = get_resource_path("data/icons-1/uitubiao.ico") + icon_path = get_resource_path("data/icons-1/app.ico") self.setWindowIcon(QIcon(icon_path)) # 第一步:创建各 Manager(纯连线,不执行业务) diff --git a/water_quality_app.spec b/water_quality_app.spec new file mode 100644 index 0000000..91df30a --- /dev/null +++ b/water_quality_app.spec @@ -0,0 +1,225 @@ +# -*- 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, +) \ No newline at end of file