327 lines
8.7 KiB
Python
327 lines
8.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
EG 打包分析工具
|
|
用于分析项目结构和打包配置的完整性
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List, Set, Tuple
|
|
|
|
# 项目根目录
|
|
PROJECT_ROOT = Path(__file__).parent.parent.absolute()
|
|
|
|
# 当前的打包配置 (从 setup.py 同步)
|
|
DATA_DIRECTORIES = [
|
|
# 配置和资源
|
|
"config",
|
|
"icons",
|
|
"Resources",
|
|
"tex",
|
|
"templates",
|
|
|
|
# 核心模块
|
|
"core",
|
|
"gui",
|
|
"project",
|
|
"scene",
|
|
"scripts",
|
|
"ssbo_component",
|
|
"tools",
|
|
"TransformGizmo",
|
|
"ui",
|
|
|
|
# 引擎和扩展
|
|
"RenderPipelineFile",
|
|
"vr_actions",
|
|
|
|
# 项目模板
|
|
"new",
|
|
]
|
|
|
|
# 排除的模式
|
|
EXCLUDE_PATTERNS = [
|
|
"__pycache__",
|
|
"*.pyc",
|
|
"*.pyo",
|
|
"*.pyd",
|
|
".git",
|
|
".gitignore",
|
|
".idea",
|
|
".vscode",
|
|
"*.egg-info",
|
|
"*.egg",
|
|
"dist",
|
|
"build",
|
|
"*.spec",
|
|
]
|
|
|
|
# 已知的开发/构建目录 (不应该打包)
|
|
DEV_DIRECTORIES = [
|
|
".git",
|
|
".idea",
|
|
".vscode",
|
|
"build",
|
|
"build_scripts",
|
|
"dist",
|
|
"test_project", # 示例项目,可选
|
|
"__pycache__",
|
|
]
|
|
|
|
# 已知的文档文件 (可选打包)
|
|
DOC_FILES = [
|
|
"README.md",
|
|
"LICENSE.txt",
|
|
"CHANGELOG.md",
|
|
"AGENTS.md",
|
|
]
|
|
|
|
|
|
def get_directory_size(path: Path) -> Tuple[int, int]:
|
|
"""计算目录大小和文件数量"""
|
|
total_size = 0
|
|
file_count = 0
|
|
|
|
if not path.exists():
|
|
return 0, 0
|
|
|
|
for item in path.rglob("*"):
|
|
if item.is_file():
|
|
# 检查是否应该排除
|
|
should_exclude = False
|
|
for pattern in EXCLUDE_PATTERNS:
|
|
if pattern.startswith("*"):
|
|
if item.name.endswith(pattern[1:]):
|
|
should_exclude = True
|
|
break
|
|
elif pattern in str(item.relative_to(path)):
|
|
should_exclude = True
|
|
break
|
|
|
|
if not should_exclude:
|
|
total_size += item.stat().st_size
|
|
file_count += 1
|
|
|
|
return total_size, file_count
|
|
|
|
|
|
def format_size(size_bytes: int) -> str:
|
|
"""格式化文件大小"""
|
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
if size_bytes < 1024:
|
|
return f"{size_bytes:.2f} {unit}"
|
|
size_bytes /= 1024
|
|
return f"{size_bytes:.2f} TB"
|
|
|
|
|
|
def analyze_project_structure():
|
|
"""分析项目结构"""
|
|
print("=" * 70)
|
|
print("EG 项目打包分析")
|
|
print("=" * 70)
|
|
print()
|
|
|
|
# 获取所有目录
|
|
all_dirs = [d for d in PROJECT_ROOT.iterdir() if d.is_dir()]
|
|
all_dir_names = {d.name for d in all_dirs}
|
|
|
|
# 分类
|
|
packaged_dirs = []
|
|
dev_dirs = []
|
|
missing_dirs = []
|
|
unknown_dirs = []
|
|
|
|
for d in all_dirs:
|
|
name = d.name
|
|
if name in DATA_DIRECTORIES:
|
|
packaged_dirs.append(d)
|
|
elif name in DEV_DIRECTORIES:
|
|
dev_dirs.append(d)
|
|
elif name.startswith(".") or name in ["new", "test_project"]:
|
|
# 特殊目录
|
|
if name not in DATA_DIRECTORIES:
|
|
missing_dirs.append(d)
|
|
else:
|
|
unknown_dirs.append(d)
|
|
|
|
# 1. 已配置的打包目录
|
|
print("📦 已配置的打包目录:")
|
|
print("-" * 70)
|
|
total_size = 0
|
|
total_files = 0
|
|
|
|
for d in sorted(packaged_dirs, key=lambda x: x.name):
|
|
size, count = get_directory_size(d)
|
|
total_size += size
|
|
total_files += count
|
|
status = "✓" if d.exists() else "✗ 不存在"
|
|
print(f" {status:12} {d.name:25} {format_size(size):>12} ({count} 文件)")
|
|
|
|
print("-" * 70)
|
|
print(f" {'总计':12} {'':25} {format_size(total_size):>12} ({total_files} 文件)")
|
|
print()
|
|
|
|
# 2. 开发目录 (正确排除)
|
|
if dev_dirs:
|
|
print("🔧 开发/构建目录 (正确排除):")
|
|
print("-" * 70)
|
|
for d in sorted(dev_dirs, key=lambda x: x.name):
|
|
size, count = get_directory_size(d)
|
|
print(f" {'✓ 排除':12} {d.name:25} {format_size(size):>12}")
|
|
print()
|
|
|
|
# 3. 未配置的目录 (需要检查)
|
|
if unknown_dirs:
|
|
print("⚠️ 未配置的目录 (请检查是否需要打包):")
|
|
print("-" * 70)
|
|
for d in sorted(unknown_dirs, key=lambda x: x.name):
|
|
size, count = get_directory_size(d)
|
|
print(f" {'? 待确认':12} {d.name:25} {format_size(size):>12} ({count} 文件)")
|
|
# 列出部分文件帮助判断
|
|
files = list(d.iterdir())[:5]
|
|
for f in files:
|
|
file_type = "📁" if f.is_dir() else "📄"
|
|
print(f" {file_type} {f.name}")
|
|
if len(list(d.iterdir())) > 5:
|
|
print(f" ... 等 {len(list(d.iterdir())) - 5} 个文件")
|
|
print()
|
|
|
|
# 4. 检查配置中但目录不存在的
|
|
config_only = set(DATA_DIRECTORIES) - all_dir_names
|
|
if config_only:
|
|
print("⚠️ 配置中有但目录不存在:")
|
|
print("-" * 70)
|
|
for name in config_only:
|
|
print(f" ✗ 不存在 {name}")
|
|
print()
|
|
|
|
# 5. 关键文件检查
|
|
print("🔑 关键文件检查:")
|
|
print("-" * 70)
|
|
|
|
critical_files = {
|
|
"Start_Run.py": "入口脚本",
|
|
"main.py": "主程序",
|
|
"imgui.ini": "ImGui配置",
|
|
"icons/app.ico": "Windows图标",
|
|
"icons/app.png": "Linux图标",
|
|
}
|
|
|
|
for file, desc in critical_files.items():
|
|
path = PROJECT_ROOT / file
|
|
exists = "✓" if path.exists() else "✗"
|
|
print(f" {exists:3} {file:25} ({desc})")
|
|
print()
|
|
|
|
# 6. 建议
|
|
print("💡 建议:")
|
|
print("-" * 70)
|
|
|
|
suggestions = []
|
|
|
|
if unknown_dirs:
|
|
suggestions.append(f"有 {len(unknown_dirs)} 个未配置目录,请检查是否需要打包")
|
|
|
|
if config_only:
|
|
suggestions.append(f"配置中有 {len(config_only)} 个目录不存在,请更新配置")
|
|
|
|
# 检查图标
|
|
if not (PROJECT_ROOT / "icons/app.ico").exists():
|
|
suggestions.append("缺少 Windows 图标: icons/app.ico")
|
|
if not (PROJECT_ROOT / "icons/app.png").exists():
|
|
suggestions.append("缺少 Linux 图标: icons/app.png")
|
|
|
|
# 检查大小
|
|
if total_size > 500 * 1024 * 1024:
|
|
suggestions.append(f"打包内容较大 ({format_size(total_size)}),考虑优化")
|
|
|
|
if not suggestions:
|
|
print(" ✓ 一切正常!")
|
|
else:
|
|
for s in suggestions:
|
|
print(f" • {s}")
|
|
print()
|
|
|
|
# 7. 预估输出
|
|
print("📊 预估输出:")
|
|
print("-" * 70)
|
|
|
|
# Nuitka 编译后大约增加 20-50MB 开销
|
|
estimated_exe_size = total_size + 50 * 1024 * 1024
|
|
print(f" 数据文件: {format_size(total_size)}")
|
|
print(f" 编译开销(预估): ~50 MB")
|
|
print(f" 总计(预估): {format_size(estimated_exe_size)}")
|
|
print(f" 压缩后(预估): {format_size(estimated_exe_size * 0.6)}") # 假设 60% 压缩率
|
|
print()
|
|
|
|
# 返回是否有问题
|
|
has_issues = bool(unknown_dirs or config_only or not suggestions)
|
|
return has_issues
|
|
|
|
|
|
def generate_updated_config():
|
|
"""生成更新后的配置代码"""
|
|
print("=" * 70)
|
|
print("建议的配置更新")
|
|
print("=" * 70)
|
|
print()
|
|
|
|
# 获取所有实际存在的目录
|
|
all_dirs = {d.name for d in PROJECT_ROOT.iterdir() if d.is_dir()}
|
|
|
|
# 自动分类
|
|
auto_include = []
|
|
auto_exclude = []
|
|
|
|
for name in sorted(all_dirs):
|
|
if name in DEV_DIRECTORIES:
|
|
auto_exclude.append(name)
|
|
elif name.startswith("."):
|
|
auto_exclude.append(name)
|
|
else:
|
|
auto_include.append(name)
|
|
|
|
print("# 自动生成的配置建议:")
|
|
print()
|
|
print("DATA_DIRECTORIES = [")
|
|
for name in auto_include:
|
|
comment = ""
|
|
if name == "project":
|
|
comment = " # 项目管理模块"
|
|
elif name == "new":
|
|
comment = " # 默认项目模板"
|
|
elif name == "test_project":
|
|
comment = " # 示例项目 (可选)"
|
|
print(f' "{name}",{comment}')
|
|
print("]")
|
|
print()
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="EG 打包分析工具")
|
|
parser.add_argument("--generate", action="store_true", help="生成配置建议")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.generate:
|
|
generate_updated_config()
|
|
else:
|
|
has_issues = analyze_project_structure()
|
|
|
|
print()
|
|
print("=" * 70)
|
|
if has_issues:
|
|
print("发现潜在问题,请检查上方输出")
|
|
sys.exit(1)
|
|
else:
|
|
print("✓ 分析完成,配置看起来正常")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|