EG/build_scripts/analyze_packaging.py

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()