Harden Windows and Linux packaging
This commit is contained in:
parent
cfe92a9a80
commit
3d994dd556
57
Start_Run.py
57
Start_Run.py
@ -1,8 +1,11 @@
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
# 添加项目根目录到 Python 路径
|
||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||
# 添加项目根目录到 Python 路径。打包后可通过环境变量覆盖,
|
||||
# 以适配 Windows dist / Linux AppDir 等不同目录布局。
|
||||
project_root = os.environ.get("EG_PROJECT_ROOT") or os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
# 设置工作目录为项目根目录
|
||||
@ -20,30 +23,34 @@ sys.path.insert(0, render_pipeline_file_path)
|
||||
icons_path = os.path.join(project_root, "icons")
|
||||
sys.path.insert(0, icons_path)
|
||||
|
||||
|
||||
def _write_startup_log(message: str) -> None:
|
||||
"""Write startup diagnostics next to the executable/source root."""
|
||||
log_path = os.path.join(project_root, "eg_startup.log")
|
||||
with open(log_path, "a", encoding="utf-8") as handle:
|
||||
handle.write(f"\n[{datetime.now().isoformat(timespec='seconds')}] {message}\n")
|
||||
|
||||
# 现在可以导入并运行主程序
|
||||
if __name__ == "__main__":
|
||||
args = sys.argv[1:]
|
||||
# args = "/home/tiger/桌面/Test1"
|
||||
# args = "C:/Users/29381/Desktop/1"
|
||||
print(f'Path is {args}')
|
||||
# 将整个列表转换为字符串(包括方括号)
|
||||
args_str = ''.join(args)
|
||||
try:
|
||||
args = sys.argv[1:]
|
||||
_write_startup_log(f"Launch args: {args}")
|
||||
_write_startup_log(f"Working directory: {os.getcwd()}")
|
||||
|
||||
from main import MyWorld
|
||||
if args:
|
||||
# print(f"[DEBUG] 启动时传入项目路径: {args_str}")
|
||||
# 创建应用实例
|
||||
app = MyWorld()
|
||||
# 如果传入了项目路径,尝试打开项目
|
||||
if hasattr(app, 'project_manager'):
|
||||
# print(f"[DEBUG] 尝试打开项目: {args_str}")
|
||||
success = app.project_manager.openProject(args_str)
|
||||
# print(f"[DEBUG] 项目打开结果: {success}")
|
||||
# 将整个列表转换为字符串(包括方括号)
|
||||
args_str = ''.join(args)
|
||||
|
||||
from main import MyWorld
|
||||
|
||||
if args:
|
||||
app = MyWorld()
|
||||
if hasattr(app, 'project_manager'):
|
||||
app.project_manager.openProject(args_str)
|
||||
app.run()
|
||||
else:
|
||||
# print(f"[DEBUG] 项目管理器未初始化")
|
||||
pass
|
||||
app.run()
|
||||
else:
|
||||
# print(f"[DEBUG] 无项目路径,正常启动")
|
||||
app = MyWorld()
|
||||
app.run()
|
||||
app = MyWorld()
|
||||
app.run()
|
||||
except Exception:
|
||||
_write_startup_log("Unhandled exception during startup:")
|
||||
_write_startup_log(traceback.format_exc())
|
||||
raise
|
||||
|
||||
395
build_scripts/BUILD_GUIDE.md
Normal file
395
build_scripts/BUILD_GUIDE.md
Normal file
@ -0,0 +1,395 @@
|
||||
# 元泰 EG 构建完整指南
|
||||
|
||||
本文档提供从零开始构建元泰 EG 安装程序的完整步骤。
|
||||
|
||||
---
|
||||
|
||||
## 📋 环境要求
|
||||
|
||||
### 必须条件
|
||||
|
||||
| 组件 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| **Python** | **3.11.x** | 必须使用 3.11,其他版本不保证兼容 |
|
||||
| Windows | 10/11 | 64位系统 |
|
||||
| 磁盘空间 | 5GB+ | 构建需要大量临时文件 |
|
||||
| 内存 | 4GB+ | 建议 8GB |
|
||||
|
||||
### 检查 Python 版本
|
||||
|
||||
```powershell
|
||||
# 检查版本
|
||||
python --version
|
||||
# 必须显示: Python 3.11.x
|
||||
|
||||
# 使用项目提供的检查脚本
|
||||
python build_scripts/check_python_version.py
|
||||
```
|
||||
|
||||
如果版本不对,请参考 `build_scripts/PYTHON_VERSION_REQUIREMENT.md` 安装正确版本。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始(5分钟)
|
||||
|
||||
### 步骤 1: 安装依赖
|
||||
|
||||
```powershell
|
||||
# 确保是 Python 3.11
|
||||
python --version
|
||||
|
||||
# 安装最小依赖(推荐)
|
||||
python -m pip install -r requirements/requirements-minimal.txt
|
||||
```
|
||||
|
||||
### 步骤 2: 验证安装
|
||||
|
||||
```powershell
|
||||
# 测试程序能否运行
|
||||
python Start_Run.py
|
||||
```
|
||||
|
||||
### 步骤 3: 构建安装程序
|
||||
|
||||
```powershell
|
||||
# 进入 VS Dev Shell(必须有 C++ 编译器)
|
||||
# 然后执行构建脚本
|
||||
.\build_scripts\build_windows.ps1 -Version "1.0.0"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📌 打包原则
|
||||
|
||||
从 2026-03 的修复开始,项目采用“保守打包优先”的策略:
|
||||
|
||||
- 优先保证发行版可运行,不先追求安装包最小化
|
||||
- 对 `RenderPipeline`、动态插件、文件路径动态导入使用显式白名单
|
||||
- 将 `RenderPipelineFile`、`Resources`、`config`、`demo` 等目录视为运行时资源,整体复制
|
||||
- 保留启动日志 `eg_startup.log`,用于定位打包后启动异常
|
||||
|
||||
详细背景和源码整改建议请参考:
|
||||
|
||||
- `build_scripts/PACKAGING_RISK_AUDIT.md`
|
||||
|
||||
---
|
||||
|
||||
## 📦 依赖说明
|
||||
|
||||
### 最小依赖(requirements-minimal.txt)
|
||||
|
||||
只包含程序运行必需的依赖:
|
||||
|
||||
```
|
||||
Panda3D==1.10.16 # 3D 引擎核心
|
||||
panda3d-frame # Panda3D 扩展
|
||||
panda3d-imgui==1.1.0 # ImGui 集成
|
||||
imgui-bundle==1.92.4 # ImGui 绑定
|
||||
openvr==2.2.0 # VR 支持
|
||||
Pillow==11.3.0 # 图像处理
|
||||
PyYAML==5.4.1 # 配置解析
|
||||
psutil==5.9.0 # 系统工具
|
||||
```
|
||||
|
||||
### 已移除的依赖
|
||||
|
||||
以下依赖已从项目中移除,节省约 **170MB**:
|
||||
|
||||
| 依赖 | 节省空间 | 原因 |
|
||||
|------|---------|------|
|
||||
| PyQt5 | ~50 MB | 未使用 |
|
||||
| PySide6 | ~100 MB | 未使用 |
|
||||
| PyQt5-Qt5, PyQt5_sip 等 | ~20 MB | 配套库 |
|
||||
|
||||
### 清理旧依赖
|
||||
|
||||
如果之前安装过旧依赖,建议清理:
|
||||
|
||||
```powershell
|
||||
# 卸载未使用的 Qt 库
|
||||
python -m pip uninstall -y PyQt5 PyQt5-Qt5 PyQt5_sip PySide6 PySide6_Addons PySide6_Essentials shiboken6
|
||||
|
||||
# 清理 pip 缓存
|
||||
python -m pip cache purge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 详细构建步骤
|
||||
|
||||
### 第一步:环境准备
|
||||
|
||||
1. **安装 Python 3.11**
|
||||
- 下载:https://www.python.org/downloads/release/python-3119/
|
||||
- 安装时勾选 "Add Python to PATH"
|
||||
|
||||
2. **安装 Visual Studio Build Tools**(Windows 必需)
|
||||
- 下载:https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
||||
- 选择 "使用 C++ 的桌面开发"
|
||||
- 或安装完整 Visual Studio 2022
|
||||
|
||||
3. **验证环境**
|
||||
```powershell
|
||||
# 检查 Python
|
||||
python --version # Python 3.11.x
|
||||
|
||||
# 检查编译器(在 VS Dev Shell 中)
|
||||
cl # 应显示版本信息
|
||||
```
|
||||
|
||||
### 第二步:安装项目依赖
|
||||
|
||||
```powershell
|
||||
# 进入项目目录
|
||||
cd C:\path\to\EG
|
||||
|
||||
# 安装依赖
|
||||
python -m pip install -r requirements/requirements-minimal.txt
|
||||
|
||||
# 验证安装
|
||||
python -c "import panda3d.core; import imgui_bundle; print('✓ 依赖安装成功')"
|
||||
```
|
||||
|
||||
### 第三步:运行测试
|
||||
|
||||
```powershell
|
||||
# 测试程序能否正常启动
|
||||
python Start_Run.py
|
||||
```
|
||||
|
||||
### 第四步:打开 VS Dev Shell
|
||||
|
||||
**什么是 VS Dev Shell?**
|
||||
VS Dev Shell(Visual Studio 开发人员命令提示符)是一个包含 C++ 编译器(cl.exe)的特殊命令行窗口,Nuitka 需要它来编译 Python 代码为机器码。
|
||||
|
||||
**如何打开 VS Dev Shell:**
|
||||
|
||||
#### 方法 1: 通过开始菜单(推荐)
|
||||
|
||||
1. 按 `Win` 键打开开始菜单
|
||||
2. 搜索:`Developer Command Prompt`
|
||||
- 或中文:`开发人员命令提示符`
|
||||
3. 点击 **"Developer Command Prompt for VS 2022"**
|
||||
4. 建议右键选择 **"以管理员身份运行"**
|
||||
|
||||
#### 方法 2: 通过 Visual Studio
|
||||
|
||||
1. 打开 **Visual Studio 2022**
|
||||
2. 点击菜单栏的 **"工具"** → **"命令行"** → **"开发者命令提示符"**
|
||||
|
||||
#### 方法 3: 使用 PowerShell 命令
|
||||
|
||||
```powershell
|
||||
# 如果知道 VS 安装路径
|
||||
& "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64
|
||||
```
|
||||
|
||||
**验证 VS Dev Shell 已正确打开:**
|
||||
|
||||
```powershell
|
||||
# 在 VS Dev Shell 中执行
|
||||
cl
|
||||
|
||||
# 应该显示类似:
|
||||
# 用于 x64 的 Microsoft (R) C/C++ 优化编译器 19.xx.xxxxx 版
|
||||
```
|
||||
|
||||
### 第五步:构建安装程序
|
||||
|
||||
在 **VS Dev Shell** 中执行:
|
||||
|
||||
#### 方法 A:使用构建脚本(推荐)
|
||||
|
||||
```powershell
|
||||
# 1. 进入项目目录
|
||||
cd C:\Users\Tellme\apps\EG
|
||||
|
||||
# 2. 切换到 PowerShell(如果当前是 cmd)
|
||||
powershell
|
||||
|
||||
# 3. 执行构建
|
||||
.\build_scripts\build_windows.ps1 -Version "1.0.0"
|
||||
```
|
||||
|
||||
该脚本已包含以下关键修复:
|
||||
|
||||
- 修正 `RenderPipeline` 在打包后可能找错 `config/pipeline.yaml` 的问题
|
||||
- 显式包含 `rpcore`、`rplibs`、`rpplugins`
|
||||
- 自动探测并尽量纳入 `plugins`、`gltf`、`playwright`、`PyQt5` 等可选包
|
||||
- 自动复制运行时资源目录
|
||||
|
||||
#### 方法 B:手动构建
|
||||
|
||||
```powershell
|
||||
# 在 VS Dev Shell 中
|
||||
cd C:\path\to\EG
|
||||
|
||||
python -m nuitka `
|
||||
--standalone `
|
||||
--include-package=panda3d `
|
||||
--include-package=direct `
|
||||
--include-package=imgui_bundle `
|
||||
--include-package=p3dimgui `
|
||||
--include-package=openvr `
|
||||
--include-package=RenderPipelineFile `
|
||||
--include-package=rpcore `
|
||||
--include-package=rplibs `
|
||||
--include-package=rpplugins `
|
||||
--follow-import-to=rpcore `
|
||||
--follow-import-to=rpplugins `
|
||||
--include-data-files="$env:APPDATA\Python\Python311\site-packages\imgui_bundle\glfw3.dll=imgui_bundle\glfw3.dll" `
|
||||
--windows-icon-from-ico=icons/app.ico `
|
||||
--windows-disable-console `
|
||||
--output-dir=build/nuitka `
|
||||
--jobs=4 `
|
||||
Start_Run.py
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 当前更推荐 `standalone + 资源目录复制` 的方式,而不是 `onefile`
|
||||
- `RenderPipeline`、动态插件和外部资源目录在 `onefile` 下更容易出现路径问题
|
||||
|
||||
### 第五步:打包资源
|
||||
|
||||
构建完成后,手动复制资源文件:
|
||||
|
||||
```powershell
|
||||
# 创建输出目录
|
||||
$Version = "1.0.0"
|
||||
New-Item -ItemType Directory -Force -Path "dist\EG_$Version" | Out-Null
|
||||
|
||||
# 复制可执行文件
|
||||
Copy-Item "build\nuitka\Start_Run.exe" "dist\EG_$Version\元泰.exe" -Force
|
||||
|
||||
# 复制资源目录
|
||||
$dirs = @("config", "icons", "Resources", "tex", "templates",
|
||||
"core", "gui", "project", "scene", "scripts",
|
||||
"ssbo_component", "tools", "TransformGizmo", "ui",
|
||||
"RenderPipelineFile", "vr_actions", "new")
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
Copy-Item $dir "dist\EG_$Version\$dir" -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Host "✓ 打包完成: dist\EG_$Version"
|
||||
```
|
||||
|
||||
如果使用 `build_windows.ps1`,通常不需要再手动执行这一段,因为脚本已经自动复制资源。
|
||||
|
||||
### Linux 构建说明
|
||||
|
||||
Linux 构建脚本:
|
||||
|
||||
```bash
|
||||
./build_scripts/build_linux.sh "1.0.0"
|
||||
```
|
||||
|
||||
Linux 脚本已按与 Windows 相同的思路补强:
|
||||
|
||||
- 使用保守白名单显式包含核心包
|
||||
- 自动探测并尽量纳入可选依赖
|
||||
- 整体复制运行时资源目录
|
||||
- 在 AppRun 中设置 `EG_PROJECT_ROOT`
|
||||
- 让 AppImage 内的程序从真实资源根目录启动
|
||||
|
||||
---
|
||||
|
||||
## 📁 构建输出
|
||||
|
||||
构建完成后,输出目录结构:
|
||||
|
||||
```
|
||||
dist/EG_1.0.0/
|
||||
├── 元泰.exe # 主程序 (~120MB)
|
||||
├── config/ # 配置文件
|
||||
├── icons/ # 图标资源
|
||||
├── Resources/ # 模型、材质等资源
|
||||
├── RenderPipelineFile/ # 渲染管线
|
||||
├── ... # 其他数据目录
|
||||
└── (无 .py 源代码) # 已编译为机器码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q1: Python 版本错误
|
||||
|
||||
**错误**:`必须使用 Python 3.11,当前版本是 3.13.5`
|
||||
|
||||
**解决**:
|
||||
```powershell
|
||||
# 安装 Python 3.11
|
||||
# 参考 build_scripts/PYTHON_VERSION_REQUIREMENT.md
|
||||
```
|
||||
|
||||
### Q2: 找不到 C++ 编译器
|
||||
|
||||
**错误**:`未找到 Visual C++ 编译器`
|
||||
|
||||
**解决**:
|
||||
1. 安装 Visual Studio Build Tools
|
||||
2. 在 **VS Dev Shell** 中运行构建
|
||||
|
||||
### Q3: 缺少 DLL
|
||||
|
||||
**错误**:`Cannot find glfw3.dll`
|
||||
|
||||
**解决**:确保 `--include-data-files` 参数正确指向 DLL 路径:
|
||||
```powershell
|
||||
# 注意 Python 版本路径
|
||||
# Python 3.11: ...\Python311\...
|
||||
# Python 3.13: ...\Python313\...
|
||||
```
|
||||
|
||||
### Q4: ModuleNotFoundError: rpcore
|
||||
|
||||
**错误**:`No module named 'rpcore'`
|
||||
|
||||
**解决**:确保使用 `--follow-import-to=rpcore`
|
||||
|
||||
### Q5: 找不到 pipeline.yaml / rpplugins.*
|
||||
|
||||
**错误示例**:
|
||||
|
||||
- `Failed to load YAML file: File not found`
|
||||
- `No such file or directory: /$$rpconfig/pipeline.yaml`
|
||||
- `ModuleNotFoundError: No module named 'rpplugins.ao'`
|
||||
|
||||
**原因**:
|
||||
|
||||
- `RenderPipeline` 在打包后可能把基路径识别错
|
||||
- 插件通过配置文件动态加载,打包器不会稳定自动发现
|
||||
|
||||
**解决**:
|
||||
|
||||
- 使用仓库中的最新 `build_windows.ps1` / `build_linux.sh`
|
||||
- 确保 `RenderPipelineFile` 被整体复制
|
||||
- 确保 `rpcore`、`rplibs`、`rpplugins` 被显式纳入打包
|
||||
|
||||
### Q6: 双击后闪退,没有控制台输出
|
||||
|
||||
**解决**:
|
||||
|
||||
- 查看发行目录中的 `eg_startup.log`
|
||||
- 该文件会记录启动阶段 traceback
|
||||
- 遇到打包问题时,优先保留这个日志进行排查
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
| 文档 | 用途 |
|
||||
|------|------|
|
||||
| `build_scripts/BUILD_README.md` | 构建说明 |
|
||||
| `build_scripts/INSTALLER_GUIDE.md` | 完整指南 |
|
||||
| `build_scripts/QUICK_REFERENCE.md` | 快速参考 |
|
||||
| `build_scripts/ICON_GUIDE.md` | 图标准备 |
|
||||
| `build_scripts/PACKAGING_RISK_AUDIT.md` | 打包风险与整改建议 |
|
||||
| `build_scripts/PYTHON_VERSION_REQUIREMENT.md` | Python 版本要求 |
|
||||
| `build_scripts/PACKAGING_CHECKLIST.md` | 打包检查清单 |
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2024年
|
||||
246
build_scripts/PACKAGING_RISK_AUDIT.md
Normal file
246
build_scripts/PACKAGING_RISK_AUDIT.md
Normal file
@ -0,0 +1,246 @@
|
||||
# 元泰 EG 打包风险与源码整改清单
|
||||
|
||||
本文档用于记录当前项目在 Windows/Linux 打包时容易导致运行失败、功能失效或行为不一致的风险点,并给出建议的整改方向。
|
||||
|
||||
目标原则:
|
||||
|
||||
- 打包优先保证可运行,不追求安装包最小化
|
||||
- 对动态导入、配置驱动加载、原生扩展依赖保持保守策略
|
||||
- 对已声明移除的依赖,源码中不应再保留残留引用
|
||||
|
||||
---
|
||||
|
||||
## 一、已确认的打包风险
|
||||
|
||||
### 1. RenderPipeline 配置与插件属于运行时动态加载
|
||||
|
||||
相关位置:
|
||||
|
||||
- [RenderPipelineFile/config/plugins.yaml](/C:/Users/Tellme/apps/EG/RenderPipelineFile/config/plugins.yaml)
|
||||
- [core/world.py](/C:/Users/Tellme/apps/EG/core/world.py)
|
||||
- [build_scripts/build_windows.ps1](/C:/Users/Tellme/apps/EG/build_scripts/build_windows.ps1)
|
||||
|
||||
问题说明:
|
||||
|
||||
- `RenderPipeline` 会根据配置文件在运行时动态加载 `rpplugins.*`
|
||||
- 这类插件不是普通静态 import,打包工具无法稳定自动发现
|
||||
- 已实际出现 `ModuleNotFoundError: No module named 'rpplugins.ao'`
|
||||
|
||||
建议:
|
||||
|
||||
- 打包脚本显式包含 `rpcore`、`rplibs`、`rpplugins`
|
||||
- 保留整个 `RenderPipelineFile` 目录
|
||||
- 显式设置 `RenderPipeline` 的 `base_path` 和 `config_dir`
|
||||
- 后续新增插件时,同步检查打包白名单
|
||||
|
||||
### 2. 文件路径驱动的动态模块加载
|
||||
|
||||
相关位置:
|
||||
|
||||
- [main.py](/C:/Users/Tellme/apps/EG/main.py)
|
||||
- [core/script_system.py](/C:/Users/Tellme/apps/EG/core/script_system.py)
|
||||
|
||||
问题说明:
|
||||
|
||||
- 使用 `importlib.util.spec_from_file_location()` 按文件路径加载模块
|
||||
- 例如 `demo/video_integration.py`
|
||||
- 这类模块不会被打包器通过静态分析自动发现
|
||||
|
||||
建议:
|
||||
|
||||
- 继续将对应目录视为运行时资源并整体复制
|
||||
- 更理想的方案是逐步改为标准包导入
|
||||
- 所有此类动态加载点都应登记到打包白名单
|
||||
|
||||
### 3. 运行时外部工具依赖目录结构
|
||||
|
||||
相关位置:
|
||||
|
||||
- [core/world.py](/C:/Users/Tellme/apps/EG/core/world.py)
|
||||
- [core/tool_manager.py](/C:/Users/Tellme/apps/EG/core/tool_manager.py)
|
||||
|
||||
问题说明:
|
||||
|
||||
- 程序会在运行时启动 `toolkit/day_time_editor/main.py`
|
||||
- 还会启动 `toolkit/material_editor/main.py`
|
||||
- 还会启动 `toolkit/plugin_configurator/main.py`
|
||||
- 如果这些目录未被完整复制,主程序可能能启动,但局部功能会失效
|
||||
|
||||
建议:
|
||||
|
||||
- 将整个 `RenderPipelineFile` 视为正式运行时资源
|
||||
- 子工具路径不应依赖“开发目录结构恰好存在”
|
||||
- 后续可考虑增加启动前存在性检测和更友好的错误提示
|
||||
|
||||
---
|
||||
|
||||
## 二、建议项目团队整改的源码问题
|
||||
|
||||
### 1. 残留的 PyQt5 引用需要清理
|
||||
|
||||
相关位置:
|
||||
|
||||
- [ui/icon_manager.py](/C:/Users/Tellme/apps/EG/ui/icon_manager.py)
|
||||
- [core/selection.py](/C:/Users/Tellme/apps/EG/core/selection.py)
|
||||
- [scene/scene_manager_convert_tiles_mixin.py](/C:/Users/Tellme/apps/EG/scene/scene_manager_convert_tiles_mixin.py)
|
||||
- [core/world.py](/C:/Users/Tellme/apps/EG/core/world.py)
|
||||
|
||||
问题说明:
|
||||
|
||||
- 项目文档中已经说明移除了 `PyQt5/PySide6`
|
||||
- 但源码仍保留多处 `PyQt5` 引用
|
||||
- 这会导致打包后功能行为不一致,或在特定分支下运行时报错
|
||||
|
||||
建议:
|
||||
|
||||
- 用当前实际 UI 栈替代残留 Qt 逻辑
|
||||
- 删除仅为兼容历史方案保留的无效 Qt 代码
|
||||
- 对必须保留的兼容逻辑,至少统一做降级处理和提示
|
||||
|
||||
### 2. Playwright 浏览器依赖不应处于“隐式依赖”状态
|
||||
|
||||
相关位置:
|
||||
|
||||
- [core/imgui_webview.py](/C:/Users/Tellme/apps/EG/core/imgui_webview.py)
|
||||
|
||||
问题说明:
|
||||
|
||||
- 该模块依赖 `playwright.async_api`
|
||||
- 同时还依赖 Chromium 浏览器安装
|
||||
- 如果发行版没有附带这些条件,对应功能会直接不可用
|
||||
|
||||
建议:
|
||||
|
||||
- 明确决定是否正式支持此功能
|
||||
- 如果正式支持,则纳入依赖与打包流程
|
||||
- 如果不正式支持,则在发行版中禁用并提示,而不是运行时才报错
|
||||
|
||||
### 3. glTF 专用加载依赖缺少明确归档
|
||||
|
||||
相关位置:
|
||||
|
||||
- [ui/panels/animation_tools.py](/C:/Users/Tellme/apps/EG/ui/panels/animation_tools.py)
|
||||
|
||||
问题说明:
|
||||
|
||||
- 这里存在运行时 `import gltf`
|
||||
- 这不是启动必经路径,因此问题可能只在用户点击特定功能时暴露
|
||||
|
||||
建议:
|
||||
|
||||
- 如果该功能是正式能力,则将 `gltf` 明确加入依赖清单和打包清单
|
||||
- 如果只是补充路径,应在缺模块时给出明确降级提示
|
||||
|
||||
### 4. 平台特定依赖应显式治理
|
||||
|
||||
相关位置:
|
||||
|
||||
- [core/tool_manager.py](/C:/Users/Tellme/apps/EG/core/tool_manager.py)
|
||||
|
||||
问题说明:
|
||||
|
||||
- 这里使用 `win32gui`、`win32con`
|
||||
- 当前代码虽然做了异常兜底,但功能可能 silently fail
|
||||
|
||||
建议:
|
||||
|
||||
- 明确是否正式依赖 pywin32
|
||||
- 如果依赖,则加入打包环境
|
||||
- 如果不依赖,则删除窗口激活逻辑或改为可观测提示
|
||||
|
||||
### 5. 硬编码开发机路径需要移除
|
||||
|
||||
相关位置:
|
||||
|
||||
- [core/world.py](/C:/Users/Tellme/apps/EG/core/world.py)
|
||||
|
||||
问题说明:
|
||||
|
||||
- 存在 `/home/tiger/...` 这类硬编码本地路径
|
||||
- 即使当前分支未使用,也不应保留在仓库中
|
||||
|
||||
建议:
|
||||
|
||||
- 清理所有开发机私有绝对路径
|
||||
- 改为项目资源路径、配置路径或用户可配置路径
|
||||
|
||||
---
|
||||
|
||||
## 三、建议长期执行的工程规则
|
||||
|
||||
### 1. 建立“动态导入登记表”
|
||||
|
||||
所有以下模式都必须登记并同步给打包脚本:
|
||||
|
||||
- `importlib.import_module`
|
||||
- `spec_from_file_location`
|
||||
- `__import__`
|
||||
- 配置文件驱动插件加载
|
||||
- 子进程启动外部 Python 工具
|
||||
|
||||
### 2. 建立“运行时资源目录白名单”
|
||||
|
||||
以下内容不应依赖打包器自动分析:
|
||||
|
||||
- 配置文件
|
||||
- shader
|
||||
- 模型
|
||||
- 字体
|
||||
- 图标
|
||||
- demo 脚本
|
||||
- 插件目录
|
||||
- 工具目录
|
||||
|
||||
### 3. 已移除依赖必须同步清理源码
|
||||
|
||||
如果项目文档声明:
|
||||
|
||||
- 已移除 `PyQt5`
|
||||
- 已移除 `PySide6`
|
||||
|
||||
则源码中不应继续保留这些引用,避免“文档说没有,代码里还在偷偷依赖”的情况。
|
||||
|
||||
### 4. 可选功能必须有明确产品策略
|
||||
|
||||
对以下类型的依赖,需要明确归类:
|
||||
|
||||
- 浏览器内核
|
||||
- glTF 专用加载器
|
||||
- 平台专用 API
|
||||
- 外部命令行工具
|
||||
|
||||
只能二选一:
|
||||
|
||||
- 正式支持并纳入安装/打包流程
|
||||
- 默认禁用并提供明确提示
|
||||
|
||||
不应继续保持“开发环境可用、发行版碰运气”的状态。
|
||||
|
||||
---
|
||||
|
||||
## 四、当前打包策略建议
|
||||
|
||||
当前推荐策略:
|
||||
|
||||
- 尽量显式包含相关包,不追求安装包最小化
|
||||
- 尽量整体复制运行时资源目录
|
||||
- 对启动阶段异常保留日志文件,便于定位下一层问题
|
||||
|
||||
建议构建策略:
|
||||
|
||||
- 显式包含 `rpcore`、`rplibs`、`rpplugins`
|
||||
- 尽量显式包含 `core`、`scene`、`project`、`ui`、`gui`、`scripts`、`ssbo_component`、`tools`、`TransformGizmo`
|
||||
- 自动探测并尽量包含 `plugins`、`gltf`、`playwright` 等可选包
|
||||
- 整体复制 `RenderPipelineFile`、`Resources`、`config`、`demo` 等目录
|
||||
|
||||
---
|
||||
|
||||
## 五、建议团队后续执行顺序
|
||||
|
||||
1. 先确保打包脚本采用保守策略,优先恢复发行版可运行性
|
||||
2. 再逐步清理源码中的动态依赖、残留 Qt 引用、硬编码路径
|
||||
3. 最后再考虑做安装包体积优化,而不是反过来
|
||||
|
||||
---
|
||||
|
||||
最后更新:2026-03-14
|
||||
143
build_scripts/README.md
Normal file
143
build_scripts/README.md
Normal file
@ -0,0 +1,143 @@
|
||||
# build_scripts 目录说明
|
||||
|
||||
本目录包含元泰 EG 项目的所有构建相关脚本和文档。
|
||||
|
||||
---
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
build_scripts/
|
||||
├── 🔧 构建脚本
|
||||
│ ├── build_windows.ps1 # Windows 主构建脚本 ⭐
|
||||
│ ├── build_linux.sh # Linux 构建脚本
|
||||
│ ├── build_optimized.ps1 # 优化构建脚本(可选)
|
||||
│ ├── quick_build.bat # Windows 快速入口
|
||||
│ ├── quick_build.sh # Linux 快速入口
|
||||
│ └── setup.py # 构建配置(可导入)
|
||||
│
|
||||
├── 🔍 检查脚本
|
||||
│ ├── check_env.ps1 # Windows 环境检查
|
||||
│ ├── check_env.sh # Linux 环境检查
|
||||
│ ├── check_python_version.py # Python 版本检查 ⭐
|
||||
│ └── analyze_packaging.py # 打包分析工具
|
||||
│
|
||||
└── 📖 文档
|
||||
├── BUILD_GUIDE.md # 完整构建指南 ⭐⭐⭐
|
||||
├── BUILD_README.md # 构建说明
|
||||
├── INSTALLER_GUIDE.md # 详细使用指南
|
||||
├── QUICK_REFERENCE.md # 命令速查表
|
||||
├── ICON_GUIDE.md # 图标准备
|
||||
├── PACKAGING_RISK_AUDIT.md # 打包风险与源码整改清单 ⭐
|
||||
├── PYTHON_VERSION_REQUIREMENT.md # Python 3.11 要求 ⭐
|
||||
└── PACKAGING_CHECKLIST.md # 打包检查清单
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 第一次构建?
|
||||
|
||||
请按顺序阅读:
|
||||
1. `BUILD_GUIDE.md` - 完整构建步骤
|
||||
2. `PYTHON_VERSION_REQUIREMENT.md` - Python 3.11 安装
|
||||
3. `ICON_GUIDE.md` - 准备应用图标(可选)
|
||||
4. `PACKAGING_RISK_AUDIT.md` - 打包风险与源码整改清单
|
||||
|
||||
### 已经配置好环境?
|
||||
|
||||
直接使用:
|
||||
```powershell
|
||||
# Windows
|
||||
.\build_scripts\build_windows.ps1 -Version "1.0.0"
|
||||
|
||||
# Linux
|
||||
./build_scripts/build_linux.sh "1.0.0"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 关键检查点
|
||||
|
||||
构建前请确认:
|
||||
|
||||
- [ ] Python 3.11.x 已安装
|
||||
- [ ] `python build_scripts/check_python_version.py` 通过
|
||||
- [ ] `pip install -r requirements/requirements-minimal.txt` 完成
|
||||
- [ ] Visual Studio Build Tools 已安装(Windows)
|
||||
- [ ] 在 VS Dev Shell 中运行构建
|
||||
|
||||
---
|
||||
|
||||
## 🔧 核心脚本说明
|
||||
|
||||
### build_windows.ps1
|
||||
|
||||
Windows 主构建脚本,功能:
|
||||
- 检查 Python 3.11
|
||||
- 检查 C++ 编译器
|
||||
- 执行保守白名单 Nuitka 编译
|
||||
- 自动探测并尽量纳入可选依赖
|
||||
- 复制运行时资源文件
|
||||
- 生成最终输出
|
||||
|
||||
关键策略:
|
||||
- 优先保证发行版可运行,不追求最小体积
|
||||
- 显式包含 `rpcore`、`rplibs`、`rpplugins`
|
||||
- 整体复制 `RenderPipelineFile`、`Resources`、`config`、`demo` 等目录
|
||||
|
||||
### build_linux.sh
|
||||
|
||||
Linux 主构建脚本,功能:
|
||||
- 执行保守白名单 Nuitka 编译
|
||||
- 构建 AppDir / AppImage
|
||||
- 显式设置 `EG_PROJECT_ROOT`,保证 AppImage 中资源路径正确
|
||||
- 复制运行时资源文件
|
||||
|
||||
用法:
|
||||
```powershell
|
||||
./build_scripts/build_linux.sh "1.0.0" --clean
|
||||
```
|
||||
|
||||
### check_python_version.py
|
||||
|
||||
Python 版本检查工具:
|
||||
```powershell
|
||||
python build_scripts/check_python_version.py
|
||||
```
|
||||
|
||||
### analyze_packaging.py
|
||||
|
||||
打包内容分析工具:
|
||||
```powershell
|
||||
python build_scripts/analyze_packaging.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 重要变更
|
||||
|
||||
### 2024年更新
|
||||
|
||||
- ✅ 指定 Python 3.11 为必须版本
|
||||
- ✅ 移除 PyQt5/PySide6 依赖(节省 ~170MB)
|
||||
- ✅ 简化依赖配置
|
||||
- ✅ 重新组织文档结构
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
**Q: 构建失败怎么办?**
|
||||
A: 查看 `BUILD_GUIDE.md` 的"常见问题"章节
|
||||
|
||||
**Q: Python 版本不对?**
|
||||
A: 阅读 `PYTHON_VERSION_REQUIREMENT.md`
|
||||
|
||||
**Q: 缺少图标?**
|
||||
A: 参考 `ICON_GUIDE.md` 创建或下载占位图标
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2024年
|
||||
525
build_scripts/build_linux.sh
Normal file
525
build_scripts/build_linux.sh
Normal file
@ -0,0 +1,525 @@
|
||||
#!/bin/bash
|
||||
# EG Linux 构建脚本 - 使用 Nuitka 编译 + AppImage 打包(保守白名单版)
|
||||
# 用法: ./build_linux.sh [版本号] [--clean] [--skip-compile] [--deb]
|
||||
|
||||
set -e
|
||||
|
||||
# 配置
|
||||
VERSION=${1:-"1.0.0"}
|
||||
CLEAN=false
|
||||
SKIP_COMPILE=false
|
||||
BUILD_DEB=false
|
||||
|
||||
# 解析参数
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--clean)
|
||||
CLEAN=true
|
||||
shift
|
||||
;;
|
||||
--skip-compile)
|
||||
SKIP_COMPILE=true
|
||||
shift
|
||||
;;
|
||||
--deb)
|
||||
BUILD_DEB=true
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 路径配置
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DIST_DIR="$PROJECT_ROOT/dist"
|
||||
BUILD_DIR="$PROJECT_ROOT/build"
|
||||
NUITKA_DIR="$BUILD_DIR/nuitka"
|
||||
APPDIR="$BUILD_DIR/EG_$VERSION.AppDir"
|
||||
OUTPUT_NAME="EG_$VERSION"
|
||||
|
||||
# 应用信息
|
||||
APP_NAME="EG"
|
||||
COMPANY_NAME="EG Team"
|
||||
APP_DESCRIPTION="EG 3D Editor and Game Engine"
|
||||
|
||||
# 需要包含的数据目录
|
||||
DATA_DIRS=(
|
||||
# 配置和资源
|
||||
"config"
|
||||
"icons"
|
||||
"Resources"
|
||||
"tex"
|
||||
"templates"
|
||||
|
||||
# 核心模块
|
||||
"core"
|
||||
"gui"
|
||||
"project" # 项目管理模块
|
||||
"scene"
|
||||
"scripts"
|
||||
"ssbo_component"
|
||||
"tools"
|
||||
"TransformGizmo"
|
||||
"ui"
|
||||
|
||||
# 引擎和扩展
|
||||
"RenderPipelineFile"
|
||||
"vr_actions"
|
||||
|
||||
# 项目模板
|
||||
"new" # 默认空项目模板
|
||||
"demo"
|
||||
)
|
||||
|
||||
# 需要包含的单个文件
|
||||
DATA_FILES=(
|
||||
"imgui.ini"
|
||||
)
|
||||
|
||||
REQUIRED_PACKAGES=(
|
||||
"panda3d"
|
||||
"direct"
|
||||
"imgui_bundle"
|
||||
"p3dimgui"
|
||||
"openvr"
|
||||
"RenderPipelineFile"
|
||||
"rpcore"
|
||||
"rplibs"
|
||||
"rpplugins"
|
||||
"core"
|
||||
"scene"
|
||||
"project"
|
||||
"ui"
|
||||
"gui"
|
||||
"scripts"
|
||||
"ssbo_component"
|
||||
"tools"
|
||||
"TransformGizmo"
|
||||
"vr_actions"
|
||||
)
|
||||
|
||||
OPTIONAL_PACKAGES=(
|
||||
"plugins"
|
||||
"gltf"
|
||||
"playwright"
|
||||
"PyQt5"
|
||||
)
|
||||
|
||||
OPTIONAL_MODULES=(
|
||||
"playwright.async_api"
|
||||
"win32gui"
|
||||
"win32con"
|
||||
"PyQt5.QtCore"
|
||||
"PyQt5.QtGui"
|
||||
"PyQt5.QtWidgets"
|
||||
)
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 辅助函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[OK]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
local step=$1
|
||||
local total=$2
|
||||
local msg=$3
|
||||
echo ""
|
||||
echo -e "${YELLOW}[$step/$total]${NC} $msg"
|
||||
}
|
||||
|
||||
python_importable() {
|
||||
local module_name="$1"
|
||||
python3 -c "import importlib.util, sys; sys.exit(0 if importlib.util.find_spec('$module_name') else 1)" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ==================== 主流程 ====================
|
||||
|
||||
echo "========================================"
|
||||
echo " EG Linux Builder v$VERSION"
|
||||
echo "========================================"
|
||||
echo "项目路径: $PROJECT_ROOT"
|
||||
|
||||
# 步骤 1: 清理
|
||||
log_step 1 6 "清理旧构建..."
|
||||
|
||||
if [ "$CLEAN" = true ]; then
|
||||
rm -rf "$BUILD_DIR"
|
||||
rm -rf "$DIST_DIR"
|
||||
log_success "已清理所有构建文件"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
mkdir -p "$DIST_DIR"
|
||||
|
||||
# 步骤 2: 环境检查
|
||||
log_step 2 6 "检查构建环境..."
|
||||
|
||||
# 检查必要的命令
|
||||
for cmd in python3 pip3 gcc g++; do
|
||||
if ! command -v $cmd &> /dev/null; then
|
||||
log_error "$cmd 未安装"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
PYTHON_VERSION=$(python3 --version 2>&1)
|
||||
log_success "Python: $PYTHON_VERSION"
|
||||
|
||||
# 检查并安装构建依赖
|
||||
log_info "检查构建依赖..."
|
||||
pip3 install -q nuitka pyinstaller 2>/dev/null || true
|
||||
log_success "构建依赖已就绪"
|
||||
|
||||
# 安装项目依赖
|
||||
log_info "检查项目依赖..."
|
||||
pip3 install -q -r "$PROJECT_ROOT/requirements/requirements-minimal.txt" 2>/dev/null || log_warning "部分依赖可能安装失败"
|
||||
log_success "项目依赖已就绪"
|
||||
|
||||
# 步骤 3: Nuitka 编译
|
||||
if [ "$SKIP_COMPILE" = false ]; then
|
||||
log_step 3 6 "使用 Nuitka 编译主程序..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
REQUIRED_PACKAGE_ARGS=()
|
||||
for package in "${REQUIRED_PACKAGES[@]}"; do
|
||||
REQUIRED_PACKAGE_ARGS+=("--include-package=$package")
|
||||
done
|
||||
|
||||
OPTIONAL_PACKAGE_ARGS=()
|
||||
for package in "${OPTIONAL_PACKAGES[@]}"; do
|
||||
if python_importable "$package"; then
|
||||
OPTIONAL_PACKAGE_ARGS+=("--include-package=$package")
|
||||
log_info "检测到可选包并纳入打包: $package"
|
||||
fi
|
||||
done
|
||||
|
||||
OPTIONAL_MODULE_ARGS=()
|
||||
for module in "${OPTIONAL_MODULES[@]}"; do
|
||||
if python_importable "$module"; then
|
||||
OPTIONAL_MODULE_ARGS+=("--include-module=$module")
|
||||
log_info "检测到可选模块并纳入打包: $module"
|
||||
fi
|
||||
done
|
||||
|
||||
# 构建 Nuitka 选项
|
||||
NUITKA_OPTS=(
|
||||
"--standalone"
|
||||
"--follow-import-to=rpcore"
|
||||
"--follow-import-to=rpplugins"
|
||||
"--follow-import-to=core"
|
||||
"--follow-import-to=scene"
|
||||
"--follow-import-to=project"
|
||||
"--follow-import-to=ui"
|
||||
"--nofollow-import-to=tkinter,test,unittest,setuptools,pip"
|
||||
"--linux-onefile-icon=icons/app.png"
|
||||
"--lto=no"
|
||||
"--jobs=$(nproc)"
|
||||
"--output-dir=$NUITKA_DIR"
|
||||
"--remove-output"
|
||||
)
|
||||
|
||||
NUITKA_OPTS+=("${REQUIRED_PACKAGE_ARGS[@]}")
|
||||
NUITKA_OPTS+=("${OPTIONAL_PACKAGE_ARGS[@]}")
|
||||
NUITKA_OPTS+=("${OPTIONAL_MODULE_ARGS[@]}")
|
||||
|
||||
log_info "开始编译 (可能需要几分钟)..."
|
||||
python3 -m nuitka "${NUITKA_OPTS[@]}" Start_Run.py
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Nuitka 编译失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "编译成功"
|
||||
else
|
||||
log_step 3 6 "跳过编译 (--skip-compile)"
|
||||
fi
|
||||
|
||||
# 步骤 4: 准备 AppDir
|
||||
log_step 4 6 "准备 AppDir..."
|
||||
|
||||
rm -rf "$APPDIR"
|
||||
mkdir -p "$APPDIR/usr/bin"
|
||||
mkdir -p "$APPDIR/usr/lib/EG"
|
||||
mkdir -p "$APPDIR/usr/share/applications"
|
||||
mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
|
||||
mkdir -p "$APPDIR/usr/share/icons/hicolor/128x128/apps"
|
||||
mkdir -p "$APPDIR/usr/share/icons/hicolor/64x64/apps"
|
||||
mkdir -p "$APPDIR/usr/share/metainfo"
|
||||
|
||||
# 复制 Nuitka standalone 目录
|
||||
DIST_SOURCE="$NUITKA_DIR/Start_Run.dist"
|
||||
if [ ! -d "$DIST_SOURCE" ]; then
|
||||
log_error "找不到编译后的 standalone 目录: $DIST_SOURCE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -r "$DIST_SOURCE/"* "$APPDIR/usr/lib/EG/"
|
||||
|
||||
if [ -f "$APPDIR/usr/lib/EG/Start_Run.bin" ]; then
|
||||
cp "$APPDIR/usr/lib/EG/Start_Run.bin" "$APPDIR/usr/bin/EG"
|
||||
elif [ -f "$APPDIR/usr/lib/EG/Start_Run" ]; then
|
||||
cp "$APPDIR/usr/lib/EG/Start_Run" "$APPDIR/usr/bin/EG"
|
||||
else
|
||||
EXE_SOURCE=$(find "$APPDIR/usr/lib/EG" -maxdepth 1 -type f -executable | head -1)
|
||||
if [ -z "$EXE_SOURCE" ]; then
|
||||
log_error "找不到可执行文件"
|
||||
exit 1
|
||||
fi
|
||||
cp "$EXE_SOURCE" "$APPDIR/usr/bin/EG"
|
||||
fi
|
||||
|
||||
chmod +x "$APPDIR/usr/bin/EG"
|
||||
log_success "复制可执行文件和 standalone 运行时"
|
||||
|
||||
# 复制数据目录
|
||||
COPIED=0
|
||||
for dir in "${DATA_DIRS[@]}"; do
|
||||
if [ -d "$PROJECT_ROOT/$dir" ]; then
|
||||
cp -r "$PROJECT_ROOT/$dir" "$APPDIR/usr/lib/EG/"
|
||||
# 清理 Python 缓存
|
||||
find "$APPDIR/usr/lib/EG/$dir" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||
find "$APPDIR/usr/lib/EG/$dir" -name "*.pyc" -delete 2>/dev/null || true
|
||||
log_success "复制目录: $dir"
|
||||
((COPIED++))
|
||||
else
|
||||
log_warning "目录不存在: $dir"
|
||||
fi
|
||||
done
|
||||
log_success "共复制 $COPIED 个数据目录"
|
||||
|
||||
# 复制单个文件
|
||||
for file in "${DATA_FILES[@]}"; do
|
||||
if [ -f "$PROJECT_ROOT/$file" ]; then
|
||||
cp "$PROJECT_ROOT/$file" "$APPDIR/usr/lib/EG/"
|
||||
log_success "复制文件: $file"
|
||||
fi
|
||||
done
|
||||
|
||||
# 创建 .desktop 文件
|
||||
cat > "$APPDIR/usr/share/applications/EG.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Name=EG
|
||||
Name[zh_CN]=EG 编辑器
|
||||
Comment=$APP_DESCRIPTION
|
||||
Comment[zh_CN]=EG 3D 编辑器和游戏引擎
|
||||
Exec=EG
|
||||
Icon=EG
|
||||
Type=Application
|
||||
Categories=Graphics;3DGraphics;Development;Game;
|
||||
MimeType=application/x-eg-project;
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
StartupWMClass=EG
|
||||
Keywords=3D;Editor;Game;Engine;Panda3D;
|
||||
EOF
|
||||
|
||||
# 创建 AppStream metadata (用于软件中心)
|
||||
cat > "$APPDIR/usr/share/metainfo/EG.appdata.xml" << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.yourcompany.EG</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>Proprietary</project_license>
|
||||
<name>EG</name>
|
||||
<summary>3D Editor and Game Engine</summary>
|
||||
<description>
|
||||
<p>EG is a powerful 3D editor and game engine based on Panda3D.</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">EG.desktop</launchable>
|
||||
<url type="homepage">https://your-website.com</url>
|
||||
<provides>
|
||||
<binary>EG</binary>
|
||||
</provides>
|
||||
</component>
|
||||
EOF
|
||||
|
||||
# 复制图标
|
||||
for size in 256 128 64; do
|
||||
ICON_SRC="$PROJECT_ROOT/icons/app_${size}.png"
|
||||
if [ -f "$ICON_SRC" ]; then
|
||||
cp "$ICON_SRC" "$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps/EG.png"
|
||||
elif [ -f "$PROJECT_ROOT/icons/app.png" ]; then
|
||||
# 如果只有一张图,复制到所有尺寸
|
||||
cp "$PROJECT_ROOT/icons/app.png" "$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps/EG.png"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# 创建 AppRun
|
||||
cat > "$APPDIR/AppRun" << 'EOF'
|
||||
#!/bin/bash
|
||||
# AppRun for EG
|
||||
|
||||
SELF=$(readlink -f "$0")
|
||||
HERE=${SELF%/*}
|
||||
|
||||
# 设置环境变量
|
||||
export PATH="${HERE}/usr/bin:${PATH}"
|
||||
export EG_PROJECT_ROOT="${HERE}/usr/lib/EG"
|
||||
export LD_LIBRARY_PATH="${HERE}/usr/lib/EG:${HERE}/usr/lib:${LD_LIBRARY_PATH}"
|
||||
export PYTHONPATH="${HERE}/usr/lib/EG:${HERE}/usr/lib/EG/RenderPipelineFile:${PYTHONPATH}"
|
||||
|
||||
# 设置 XDG 目录
|
||||
export XDG_DATA_DIRS="${HERE}/usr/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
|
||||
|
||||
# 切换到项目根目录,确保相对资源路径与 Windows 打包版保持一致
|
||||
cd "${EG_PROJECT_ROOT}" || exit 1
|
||||
|
||||
# 执行主程序
|
||||
exec "${HERE}/usr/bin/EG" "$@"
|
||||
EOF
|
||||
chmod +x "$APPDIR/AppRun"
|
||||
|
||||
# 复制 .desktop 和图标到根目录
|
||||
cp "$APPDIR/usr/share/applications/EG.desktop" "$APPDIR/"
|
||||
if [ -f "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" ]; then
|
||||
cp "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" "$APPDIR/EG.png"
|
||||
elif [ -f "$APPDIR/usr/share/icons/hicolor/128x128/apps/EG.png" ]; then
|
||||
cp "$APPDIR/usr/share/icons/hicolor/128x128/apps/EG.png" "$APPDIR/EG.png"
|
||||
elif [ -f "$APPDIR/usr/share/icons/hicolor/64x64/apps/EG.png" ]; then
|
||||
cp "$APPDIR/usr/share/icons/hicolor/64x64/apps/EG.png" "$APPDIR/EG.png"
|
||||
fi
|
||||
|
||||
log_success "AppDir 准备完成"
|
||||
|
||||
# 步骤 5: 创建 AppImage
|
||||
log_step 5 6 "创建 AppImage..."
|
||||
|
||||
cd "$DIST_DIR"
|
||||
|
||||
# 下载 appimagetool
|
||||
APPIMAGETOOL="appimagetool-x86_64.AppImage"
|
||||
if [ ! -f "$APPIMAGETOOL" ]; then
|
||||
log_info "下载 appimagetool..."
|
||||
wget -q "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" -O "$APPIMAGETOOL" || {
|
||||
log_error "下载 appimagetool 失败"
|
||||
exit 1
|
||||
}
|
||||
chmod +x "$APPIMAGETOOL"
|
||||
log_success "下载完成"
|
||||
fi
|
||||
|
||||
# 创建 AppImage
|
||||
APPIMAGE_NAME="EG-${VERSION}-x86_64.AppImage"
|
||||
log_info "构建 AppImage..."
|
||||
|
||||
# 使用 --appimage-extract-and-run 避免 fuse 依赖
|
||||
ARCH=x86_64 ./"$APPIMAGETOOL" --appimage-extract-and-run "$APPDIR" "$APPIMAGE_NAME" 2>&1 | while read line; do
|
||||
echo " $line"
|
||||
done
|
||||
|
||||
if [ -f "$APPIMAGE_NAME" ]; then
|
||||
chmod +x "$APPIMAGE_NAME"
|
||||
SIZE=$(du -h "$APPIMAGE_NAME" | cut -f1)
|
||||
log_success "AppImage 创建成功: $APPIMAGE_NAME ($SIZE)"
|
||||
else
|
||||
log_error "AppImage 创建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 可选: 创建 DEB 包
|
||||
if [ "$BUILD_DEB" = true ]; then
|
||||
log_step 5 6 "创建 DEB 包..."
|
||||
|
||||
DEB_DIR="$BUILD_DIR/eg_${VERSION}_amd64"
|
||||
mkdir -p "$DEB_DIR/DEBIAN"
|
||||
mkdir -p "$DEB_DIR/opt/EG"
|
||||
mkdir -p "$DEB_DIR/usr/share/applications"
|
||||
mkdir -p "$DEB_DIR/usr/share/icons/hicolor/256x256/apps"
|
||||
mkdir -p "$DEB_DIR/usr/bin"
|
||||
|
||||
# 控制文件
|
||||
cat > "$DEB_DIR/DEBIAN/control" << EOF
|
||||
Package: eg
|
||||
Version: $VERSION
|
||||
Section: graphics
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Depends: libgl1, libopenal1, libglu1-mesa, python3 (>= 3.8)
|
||||
Maintainer: $COMPANY_NAME <support@your-website.com>
|
||||
Description: EG 3D Editor and Game Engine
|
||||
EG is a powerful 3D editor and game engine
|
||||
based on Panda3D with advanced rendering.
|
||||
EOF
|
||||
|
||||
# 复制文件
|
||||
cp -r "$APPDIR/usr/"* "$DEB_DIR/opt/EG/"
|
||||
|
||||
# 创建启动器
|
||||
cat > "$DEB_DIR/usr/bin/EG" << EOF
|
||||
#!/bin/bash
|
||||
cd /opt/EG
|
||||
./bin/EG "\$@"
|
||||
EOF
|
||||
chmod +x "$DEB_DIR/usr/bin/EG"
|
||||
|
||||
# 复制 desktop 文件和图标
|
||||
cp "$APPDIR/usr/share/applications/EG.desktop" "$DEB_DIR/usr/share/applications/"
|
||||
if [ -f "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" ]; then
|
||||
cp "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" "$DEB_DIR/usr/share/icons/hicolor/256x256/apps/"
|
||||
fi
|
||||
|
||||
# 构建 deb
|
||||
dpkg-deb --build "$DEB_DIR" "$DIST_DIR/eg_${VERSION}_amd64.deb"
|
||||
|
||||
if [ -f "$DIST_DIR/eg_${VERSION}_amd64.deb" ]; then
|
||||
SIZE=$(du -h "$DIST_DIR/eg_${VERSION}_amd64.deb" | cut -f1)
|
||||
log_success "DEB 包创建成功: eg_${VERSION}_amd64.deb ($SIZE)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 步骤 6: 创建 tarball
|
||||
log_step 6 6 "创建 tarball..."
|
||||
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# 创建普通 tarball
|
||||
TARBALL="$DIST_DIR/EG_${VERSION}_Linux_Portable.tar.gz"
|
||||
tar -czf "$TARBALL" -C "$APPDIR/usr/lib/EG" .
|
||||
SIZE=$(du -h "$TARBALL" | cut -f1)
|
||||
log_success "Tarball 创建成功: EG_${VERSION}_Linux_Portable.tar.gz ($SIZE)"
|
||||
|
||||
# ==================== 完成 ====================
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo -e "${GREEN} 构建完成!${NC}"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo -e "${BLUE}输出文件:${NC}"
|
||||
|
||||
for file in "$DIST_DIR"/EG-*; do
|
||||
if [ -f "$file" ]; then
|
||||
name=$(basename "$file")
|
||||
size=$(du -h "$file" | cut -f1)
|
||||
echo -e " ${GREEN}✓${NC} $name (${size})"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}输出目录:${NC} $DIST_DIR"
|
||||
echo ""
|
||||
|
||||
# 清理
|
||||
rm -rf "$APPDIR"
|
||||
log_info "临时文件已清理"
|
||||
201
build_scripts/build_windows.ps1
Normal file
201
build_scripts/build_windows.ps1
Normal file
@ -0,0 +1,201 @@
|
||||
# 元泰 EG Windows 构建脚本 - Nuitka
|
||||
param([string]$Version = "1.0.0")
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProjectRoot = "C:\Users\Tellme\apps\EG"
|
||||
$BuildDir = "$ProjectRoot\build"
|
||||
$DistDir = "$ProjectRoot\dist\EG_$Version"
|
||||
$DataDirs = @(
|
||||
"config",
|
||||
"icons",
|
||||
"Resources",
|
||||
"tex",
|
||||
"templates",
|
||||
"core",
|
||||
"gui",
|
||||
"project",
|
||||
"scene",
|
||||
"scripts",
|
||||
"ssbo_component",
|
||||
"tools",
|
||||
"TransformGizmo",
|
||||
"ui",
|
||||
"RenderPipelineFile",
|
||||
"vr_actions",
|
||||
"new",
|
||||
"demo"
|
||||
)
|
||||
$DataFiles = @(
|
||||
"imgui.ini"
|
||||
)
|
||||
$RequiredPackages = @(
|
||||
"panda3d",
|
||||
"direct",
|
||||
"imgui_bundle",
|
||||
"p3dimgui",
|
||||
"openvr",
|
||||
"RenderPipelineFile",
|
||||
"rpcore",
|
||||
"rplibs",
|
||||
"rpplugins",
|
||||
"core",
|
||||
"scene",
|
||||
"project",
|
||||
"ui",
|
||||
"gui",
|
||||
"scripts",
|
||||
"ssbo_component",
|
||||
"tools",
|
||||
"TransformGizmo",
|
||||
"vr_actions"
|
||||
)
|
||||
$OptionalPackages = @(
|
||||
"plugins",
|
||||
"gltf",
|
||||
"playwright",
|
||||
"PyQt5"
|
||||
)
|
||||
$OptionalModules = @(
|
||||
"playwright.async_api",
|
||||
"win32gui",
|
||||
"win32con",
|
||||
"PyQt5.QtCore",
|
||||
"PyQt5.QtGui",
|
||||
"PyQt5.QtWidgets"
|
||||
)
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " 元泰 EG 构建 (Nuitka)" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
|
||||
# 必须在 VS Dev Shell 中运行
|
||||
if (!(Get-Command cl -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "错误: 请在 VS Dev Shell 中运行此脚本" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 清理
|
||||
Write-Host "`n[1/4] 清理..." -ForegroundColor Yellow
|
||||
Remove-Item $BuildDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $DistDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
|
||||
Write-Host " 完成" -ForegroundColor Green
|
||||
|
||||
# 设置环境
|
||||
Write-Host "`n[2/4] 设置环境..." -ForegroundColor Yellow
|
||||
$env:PYTHONPATH = "$ProjectRoot;$ProjectRoot\RenderPipelineFile"
|
||||
$env:PYTHONDONTWRITEBYTECODE = "1"
|
||||
|
||||
$imguiPath = (py -3.11 -c "import imgui_bundle, os; print(os.path.dirname(imgui_bundle.__file__))").Replace('\', '/')
|
||||
$glfwDll = "$imguiPath/glfw3.dll"
|
||||
Write-Host " glfw3.dll: $glfwDll" -ForegroundColor Gray
|
||||
|
||||
function Test-PythonImportable {
|
||||
param([string]$ModuleName)
|
||||
|
||||
& py -3.11 -c "import importlib.util, sys; sys.exit(0 if importlib.util.find_spec('$ModuleName') else 1)" | Out-Null
|
||||
return ($LASTEXITCODE -eq 0)
|
||||
}
|
||||
|
||||
$requiredPackageArgs = @()
|
||||
foreach ($package in $RequiredPackages) {
|
||||
$requiredPackageArgs += "--include-package=$package"
|
||||
}
|
||||
|
||||
$detectedOptionalPackageArgs = @()
|
||||
foreach ($package in $OptionalPackages) {
|
||||
if (Test-PythonImportable $package) {
|
||||
$detectedOptionalPackageArgs += "--include-package=$package"
|
||||
Write-Host " 检测到可选包并纳入打包: $package" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
$detectedOptionalModuleArgs = @()
|
||||
foreach ($module in $OptionalModules) {
|
||||
if (Test-PythonImportable $module) {
|
||||
$detectedOptionalModuleArgs += "--include-module=$module"
|
||||
Write-Host " 检测到可选模块并纳入打包: $module" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
# Nuitka 编译
|
||||
Write-Host "`n[3/4] Nuitka 编译 (预计 20-40 分钟)..." -ForegroundColor Yellow
|
||||
Write-Host " 关键修复: 使用正斜杠作为目标路径" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
$nuitkaArgs = @(
|
||||
"-m", "nuitka"
|
||||
"--standalone"
|
||||
"--follow-import-to=rpcore"
|
||||
"--follow-import-to=rpplugins"
|
||||
"--follow-import-to=core"
|
||||
"--follow-import-to=scene"
|
||||
"--follow-import-to=project"
|
||||
"--follow-import-to=ui"
|
||||
"--nofollow-import-to=tkinter,test,unittest,setuptools,pip"
|
||||
# 关键: 正斜杠作为目标路径
|
||||
"--include-data-files=$glfwDll=imgui_bundle/glfw3.dll"
|
||||
"--windows-icon-from-ico=icons/app.ico"
|
||||
"--windows-console-mode=disable"
|
||||
"--windows-company-name=元泰"
|
||||
"--windows-product-name=元泰 EG"
|
||||
"--windows-file-version=$Version"
|
||||
"--windows-product-version=$Version"
|
||||
"--output-dir=$BuildDir"
|
||||
"--jobs=8"
|
||||
"--lto=no"
|
||||
"Start_Run.py"
|
||||
)
|
||||
|
||||
$nuitkaArgs += $requiredPackageArgs
|
||||
$nuitkaArgs += $detectedOptionalPackageArgs
|
||||
$nuitkaArgs += $detectedOptionalModuleArgs
|
||||
|
||||
& py -3.11 @nuitkaArgs
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "`n[错误] 编译失败!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "`n 编译成功" -ForegroundColor Green
|
||||
|
||||
# 复制到输出目录
|
||||
Write-Host "`n[4/4] 整理输出..." -ForegroundColor Yellow
|
||||
$sourceDir = "$BuildDir\Start_Run.dist"
|
||||
Copy-Item "$sourceDir\*" $DistDir -Recurse -Force
|
||||
|
||||
# 复制项目运行时资源。Nuitka 会收集 Python 模块,但这些目录里的配置、
|
||||
# shader、模型、图标等仍然需要以真实文件形式存在于 dist 中。
|
||||
foreach ($dir in $DataDirs) {
|
||||
$sourcePath = Join-Path $ProjectRoot $dir
|
||||
if (Test-Path $sourcePath) {
|
||||
Copy-Item $sourcePath (Join-Path $DistDir $dir) -Recurse -Force
|
||||
Write-Host " 已复制目录: $dir" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($file in $DataFiles) {
|
||||
$sourcePath = Join-Path $ProjectRoot $file
|
||||
if (Test-Path $sourcePath) {
|
||||
Copy-Item $sourcePath $DistDir -Force
|
||||
Write-Host " 已复制文件: $file" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
# 重命名 exe
|
||||
if (Test-Path "$DistDir\Start_Run.exe") {
|
||||
Rename-Item "$DistDir\Start_Run.exe" "元泰.exe"
|
||||
}
|
||||
|
||||
$exeSize = [math]::Round((Get-Item "$DistDir\元泰.exe").Length / 1MB, 2)
|
||||
Write-Host " 元泰.exe ($exeSize MB)" -ForegroundColor Green
|
||||
|
||||
# 完成
|
||||
Write-Host "`n========================================" -ForegroundColor Green
|
||||
Write-Host " 构建完成!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host "输出: $DistDir\元泰.exe" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "测试运行:" -ForegroundColor Yellow
|
||||
Write-Host " $DistDir\元泰.exe" -ForegroundColor Gray
|
||||
@ -14,8 +14,9 @@ from direct.showbase.ShowBase import ShowBase
|
||||
from direct.showbase.ShowBaseGlobal import globalClock
|
||||
from scene.scene_manager import SceneManager
|
||||
|
||||
# 设置 RenderPipelineFile 路径
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
# 设置 RenderPipelineFile 路径。打包后可通过环境变量显式指定项目根目录,
|
||||
# 避免 AppImage / 独立发行目录的布局差异导致资源定位错误。
|
||||
project_root = os.environ.get("EG_PROJECT_ROOT") or os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
render_pipeline_path = os.path.join(project_root, "RenderPipelineFile")
|
||||
sys.path.insert(0, render_pipeline_path)
|
||||
|
||||
@ -65,8 +66,16 @@ class CoreWorld(ShowBase):
|
||||
if is_fullscreen:
|
||||
loadPrcFileData("", "fullscreen #t")
|
||||
|
||||
# 创建渲染管线
|
||||
# 创建渲染管线。打包后 Nuitka 会把 rpcore/rplibs 平铺到发行目录,
|
||||
# 自动探测到的 base_path 可能变成 dist 根目录,导致找不到
|
||||
# RenderPipelineFile/config/pipeline.yaml,这里显式指回真实管线目录。
|
||||
self.render_pipeline = RenderPipeline()
|
||||
rp_base_dir = os.path.join(project_root, "RenderPipelineFile")
|
||||
rp_config_dir = os.path.join(rp_base_dir, "config")
|
||||
if os.path.isdir(rp_base_dir):
|
||||
self.render_pipeline.mount_mgr.base_path = rp_base_dir
|
||||
if os.path.isdir(rp_config_dir):
|
||||
self.render_pipeline.mount_mgr.config_dir = rp_config_dir
|
||||
self.render_pipeline.pre_showbase_init()
|
||||
|
||||
# 强制开启多线程支持 (Video播放必需)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user