mirror of
https://github.com/zhouxiaoka/autoclip.git
synced 2026-09-03 06:24:14 +08:00
Squashed merge of fix/problem-fixes-from-main. - Desktop client (Tauri v2) wiring + packaging - CI workflow updates (Python 3.11, Rust toolchain, Tauri CLI, WebKit deps) - Backend test fix (test_missing_api_key respects CI-injected env var) - build_backend.py: support CI without venv + Windows-safe ASCII output Desktop build workflows (Linux/Windows/macOS) still failing — tracked in #65.
192 lines
6.9 KiB
Python
Executable File
192 lines
6.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
安装包体积检查脚本
|
||
用于检查 Tauri 构建产物的体积,确保不超过预设阈值
|
||
"""
|
||
|
||
import argparse
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
from typing import Dict, List, Tuple
|
||
|
||
class Colors:
|
||
RED = '\033[0;31m'
|
||
GREEN = '\033[0;32m'
|
||
YELLOW = '\033[1;33m'
|
||
BLUE = '\033[0;34m'
|
||
NC = '\033[0m' # No Color
|
||
|
||
class BundleSizeChecker:
|
||
def __init__(self, project_root: Path, max_size_mb: int = 500):
|
||
self.project_root = project_root
|
||
self.max_size_mb = max_size_mb
|
||
self.max_size_bytes = max_size_mb * 1024 * 1024
|
||
|
||
# Tauri 构建产物路径
|
||
self.bundle_paths = [
|
||
project_root / "src-tauri" / "target" / "release" / "bundle",
|
||
project_root / "dist",
|
||
project_root / "build"
|
||
]
|
||
|
||
def log(self, message: str, color: str = Colors.NC):
|
||
"""打印带颜色的日志"""
|
||
print(f"{color}{message}{Colors.NC}")
|
||
|
||
def get_file_size(self, file_path: Path) -> int:
|
||
"""获取文件大小(字节)"""
|
||
try:
|
||
return file_path.stat().st_size
|
||
except (OSError, FileNotFoundError):
|
||
return 0
|
||
|
||
def get_dir_size(self, dir_path: Path) -> int:
|
||
"""获取目录总大小(字节)"""
|
||
total_size = 0
|
||
try:
|
||
for item in dir_path.rglob('*'):
|
||
if item.is_file():
|
||
total_size += self.get_file_size(item)
|
||
except (OSError, FileNotFoundError):
|
||
pass
|
||
return total_size
|
||
|
||
def format_size(self, size_bytes: int) -> str:
|
||
"""格式化文件大小"""
|
||
if size_bytes < 1024:
|
||
return f"{size_bytes} B"
|
||
elif size_bytes < 1024 * 1024:
|
||
return f"{size_bytes / 1024:.1f} KB"
|
||
elif size_bytes < 1024 * 1024 * 1024:
|
||
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
||
else:
|
||
return f"{size_bytes / (1024 * 1024 * 1024):.1f} GB"
|
||
|
||
def find_bundle_files(self) -> List[Tuple[Path, int]]:
|
||
"""查找所有构建产物文件"""
|
||
bundle_files = []
|
||
|
||
for bundle_path in self.bundle_paths:
|
||
if bundle_path.exists():
|
||
if bundle_path.is_file():
|
||
size = self.get_file_size(bundle_path)
|
||
bundle_files.append((bundle_path, size))
|
||
elif bundle_path.is_dir():
|
||
# 查找常见的安装包文件
|
||
for pattern in ["*.dmg", "*.pkg", "*.deb", "*.rpm", "*.msi", "*.exe", "*.app"]:
|
||
for file_path in bundle_path.rglob(pattern):
|
||
if file_path.is_file():
|
||
size = self.get_file_size(file_path)
|
||
bundle_files.append((file_path, size))
|
||
|
||
return bundle_files
|
||
|
||
def check_resource_size(self) -> Dict[str, int]:
|
||
"""检查资源文件大小"""
|
||
resources_path = self.project_root / "src-tauri" / "resources"
|
||
resource_sizes = {}
|
||
|
||
if not resources_path.exists():
|
||
return resource_sizes
|
||
|
||
for item in resources_path.rglob('*'):
|
||
if item.is_file():
|
||
size = self.get_file_size(item)
|
||
relative_path = item.relative_to(resources_path)
|
||
resource_sizes[str(relative_path)] = size
|
||
|
||
return resource_sizes
|
||
|
||
def run_check(self, verbose: bool = False) -> bool:
|
||
"""运行体积检查"""
|
||
self.log("🔍 开始检查安装包体积", Colors.YELLOW)
|
||
self.log(f"最大允许体积: {self.format_size(self.max_size_bytes)}", Colors.YELLOW)
|
||
self.log("")
|
||
|
||
# 检查构建产物
|
||
bundle_files = self.find_bundle_files()
|
||
|
||
if not bundle_files:
|
||
self.log("⚠️ 未找到构建产物文件", Colors.YELLOW)
|
||
self.log("请确保已经运行过构建命令", Colors.YELLOW)
|
||
return True
|
||
|
||
total_size = 0
|
||
oversized_files = []
|
||
|
||
self.log("📦 构建产物文件:", Colors.BLUE)
|
||
for file_path, size in bundle_files:
|
||
total_size += size
|
||
size_str = self.format_size(size)
|
||
|
||
if size > self.max_size_bytes:
|
||
oversized_files.append((file_path, size))
|
||
self.log(f" ❌ {file_path.name}: {size_str} (超过限制)", Colors.RED)
|
||
else:
|
||
self.log(f" ✅ {file_path.name}: {size_str}", Colors.GREEN)
|
||
|
||
self.log("")
|
||
self.log(f"📊 总大小: {self.format_size(total_size)}", Colors.YELLOW)
|
||
|
||
# 检查资源文件
|
||
if verbose:
|
||
self.log("")
|
||
self.log("📁 资源文件详情:", Colors.BLUE)
|
||
resource_sizes = self.check_resource_size()
|
||
|
||
# 按大小排序
|
||
sorted_resources = sorted(resource_sizes.items(), key=lambda x: x[1], reverse=True)
|
||
|
||
for relative_path, size in sorted_resources:
|
||
size_str = self.format_size(size)
|
||
self.log(f" {relative_path}: {size_str}")
|
||
|
||
# 检查结果
|
||
if oversized_files:
|
||
self.log("")
|
||
self.log("❌ 体积检查失败!", Colors.RED)
|
||
self.log("以下文件超过体积限制:", Colors.RED)
|
||
for file_path, size in oversized_files:
|
||
size_str = self.format_size(size)
|
||
self.log(f" - {file_path.name}: {size_str}", Colors.RED)
|
||
return False
|
||
elif total_size > self.max_size_bytes:
|
||
self.log("")
|
||
self.log("❌ 体积检查失败!", Colors.RED)
|
||
self.log(f"总大小 {self.format_size(total_size)} 超过限制 {self.format_size(self.max_size_bytes)}", Colors.RED)
|
||
return False
|
||
else:
|
||
self.log("")
|
||
self.log("✅ 体积检查通过!", Colors.GREEN)
|
||
return True
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description='安装包体积检查脚本')
|
||
parser.add_argument('-m', '--max-size', type=int, default=500, help='最大允许体积(MB,默认: 500)')
|
||
parser.add_argument('-v', '--verbose', action='store_true', help='详细输出')
|
||
parser.add_argument('--project-root', type=str, help='项目根目录路径')
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 确定项目根目录
|
||
if args.project_root:
|
||
project_root = Path(args.project_root)
|
||
else:
|
||
# 从脚本位置推断项目根目录
|
||
script_dir = Path(__file__).parent
|
||
project_root = script_dir.parent
|
||
|
||
if not project_root.exists():
|
||
print(f"错误: 项目根目录不存在: {project_root}")
|
||
sys.exit(1)
|
||
|
||
# 创建检查器并运行检查
|
||
checker = BundleSizeChecker(project_root, args.max_size)
|
||
success = checker.run_check(args.verbose)
|
||
|
||
sys.exit(0 if success else 1)
|
||
|
||
if __name__ == '__main__':
|
||
main()
|