Files
diskmanager2/build.py
2026-02-09 17:59:50 +08:00

309 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Linux 存储管理器 - PyInstaller 打包脚本
自动安装依赖并打包应用程序
"""
import subprocess
import sys
import os
import shutil
from pathlib import Path
def run_command(cmd, description, check=True):
"""运行命令并输出结果"""
print(f"\n{'='*60}")
print(f" {description}")
print(f" 命令: {' '.join(cmd)}")
print(f"{'='*60}")
try:
result = subprocess.run(cmd, check=check, capture_output=False, text=True)
print(f"{description} 完成")
return result.returncode == 0
except subprocess.CalledProcessError as e:
print(f"{description} 失败")
print(f" 错误: {e}")
return False
def check_python():
"""检查 Python 版本"""
print(f"\n{'='*60}")
print(" 检查 Python 版本")
print(f"{'='*60}")
version = sys.version_info
print(f" Python 版本: {version.major}.{version.minor}.{version.micro}")
if version.major < 3 or (version.major == 3 and version.minor < 6):
print("✗ Python 版本过低,需要 Python 3.6+")
return False
print("✓ Python 版本符合要求")
return True
def is_externally_managed():
"""检测是否为 PEP 668 外部管理环境"""
# 检查是否在虚拟环境中
if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
return False
if os.environ.get('VIRTUAL_ENV'):
return False
# 检查 EXTERNALLY-MANAGED 文件
try:
import sysconfig
stdlib_path = sysconfig.get_path('stdlib')
if os.path.exists(os.path.join(stdlib_path, 'EXTERNALLY-MANAGED')):
return True
except:
pass
return False
def install_dependencies():
"""安装依赖包"""
print(f"\n{'='*60}")
print(" 安装依赖包")
print(f"{'='*60}")
dependencies = [
"pyinstaller",
"pexpect",
]
# 尝试使用 pip3 或 pip
pip_cmd = None
for pip in ["pip3", "pip"]:
if shutil.which(pip):
pip_cmd = pip
break
if not pip_cmd:
print("✗ 未找到 pip请手动安装 pip")
return False
# 检测外部管理环境
is_managed = is_externally_managed()
if is_managed:
print(" 检测到外部管理环境 (PEP 668),使用 --break-system-packages 选项")
print(f" 使用 {pip_cmd} 安装依赖...")
# 构建安装参数
extra_args = ["--break-system-packages"] if is_managed else []
# 升级 pip 本身
run_command([pip_cmd, "install", "--upgrade", "pip"] + extra_args, "升级 pip", check=False)
# 安装依赖
all_success = True
for dep in dependencies:
if not run_command([pip_cmd, "install", dep] + extra_args, f"安装 {dep}"):
print(f" 警告: {dep} 安装失败,尝试使用 sudo...")
if not run_command(["sudo", pip_cmd, "install", dep] + extra_args, f"使用 sudo 安装 {dep}", check=False):
print(f"{dep} 安装失败")
all_success = False
if not all_success:
print(f"\n{'='*60}")
print(" 依赖安装失败")
print(f"{'='*60}")
print(" 您可以选择以下方式安装:")
print("")
print(" 方式 1 - 创建虚拟环境 (推荐):")
print(" python3 -m venv venv")
print(" source venv/bin/activate")
print(" pip install pyinstaller pexpect")
print(" python3 build.py")
print("")
print(" 方式 2 - 使用系统包管理器 (Arch):")
print(" sudo pacman -S python-pyinstaller python-pexpect")
print(" python3 build.py")
print("")
print(" 方式 3 - 强制安装 (有风险):")
print(" pip3 install pyinstaller pexpect --break-system-packages")
print(" python3 build.py")
print("")
return False
return True
def clean_build():
"""清理之前的构建文件"""
print(f"\n{'='*60}")
print(" 清理构建文件")
print(f"{'='*60}")
dirs_to_remove = ["build", "dist", "__pycache__"]
files_to_remove = ["*.spec"]
removed = []
for d in dirs_to_remove:
if os.path.exists(d):
try:
shutil.rmtree(d)
removed.append(d)
except Exception as e:
print(f" 警告: 无法删除 {d}: {e}")
# 保留用户自定义的 spec 文件,只删除自动生成的
for f in os.listdir("."):
if f.startswith("mainwindow") and f.endswith(".spec") and f != "disk-manager.spec":
try:
os.remove(f)
removed.append(f)
except Exception as e:
print(f" 警告: 无法删除 {f}: {e}")
if removed:
print(f" 已清理: {', '.join(removed)}")
else:
print(" 没有需要清理的文件")
return True
def build_app():
"""使用 PyInstaller 打包应用程序"""
print(f"\n{'='*60}")
print(" 开始打包应用程序")
print(f"{'='*60}")
# 确定要打包的主文件
# 默认使用 mainwindow_tkinter.py (Tkinter 版本)
main_script = "mainwindow_tkinter.py"
if not os.path.exists(main_script):
print(f"✗ 未找到 {main_script}")
main_script = "mainwindow.py"
if not os.path.exists(main_script):
print(f"✗ 也未找到 {main_script}")
return False
print(f" 使用替代文件: {main_script}")
# 构建命令
output_name = "linux-storage-manager"
cmd = [
sys.executable, "-m", "PyInstaller",
"--onefile", # 单文件模式
"--name", output_name, # 输出文件名
"--clean", # 清理临时文件
"--noconfirm", # 不询问确认
"--console", # 显示控制台 (用于调试)
main_script
]
if not run_command(cmd, "PyInstaller 打包"):
return False
# 检查输出
output_path = Path("dist") / output_name
if output_path.exists():
size = output_path.stat().st_size / (1024 * 1024) # MB
print(f"\n{'='*60}")
print(" 打包成功!")
print(f"{'='*60}")
print(f" 输出文件: {output_path}")
print(f" 文件大小: {size:.2f} MB")
print(f"\n 使用方法:")
print(f" sudo ./{output_path}")
return True
else:
print(f"✗ 未找到输出文件: {output_path}")
return False
def create_desktop_entry():
"""创建桌面快捷方式文件"""
print(f"\n{'='*60}")
print(" 创建桌面快捷方式文件")
print(f"{'='*60}")
desktop_content = """[Desktop Entry]
Name=Linux 存储管理器
Comment=Linux 存储管理工具
Exec=/usr/local/bin/linux-storage-manager
Icon=drive-harddisk
Terminal=false
Type=Application
Categories=System;Utility;
Keywords=storage;disk;partition;raid;lvm;
"""
desktop_file = "linux-storage-manager.desktop"
try:
with open(desktop_file, "w") as f:
f.write(desktop_content)
print(f" 已创建: {desktop_file}")
print(f" 安装方法: sudo cp {desktop_file} /usr/share/applications/")
return True
except Exception as e:
print(f" 警告: 无法创建桌面文件: {e}")
return False
def main():
"""主函数"""
print("""
╔══════════════════════════════════════════════════════════════╗
║ Linux 存储管理器 - PyInstaller 打包工具 ║
╚══════════════════════════════════════════════════════════════╝
""")
# 检查是否在正确的目录
if not os.path.exists("mainwindow_tkinter.py") and not os.path.exists("mainwindow.py"):
print("✗ 错误: 未找到主程序文件")
print(" 请确保在包含 mainwindow_tkinter.py 或 mainwindow.py 的目录中运行此脚本")
sys.exit(1)
# 执行步骤
steps = [
("检查 Python 版本", check_python),
("安装依赖包", install_dependencies),
("清理构建文件", clean_build),
("打包应用程序", build_app),
("创建桌面文件", create_desktop_entry),
]
results = []
for name, func in steps:
try:
result = func()
results.append((name, result))
except Exception as e:
print(f"\n{name} 出错: {e}")
results.append((name, False))
# 打印总结
print(f"\n{'='*60}")
print(" 打包总结")
print(f"{'='*60}")
for name, result in results:
status = "✓ 成功" if result else "✗ 失败"
print(f" {status} - {name}")
# 检查最终结果
if all(r[1] for r in results):
print(f"\n{'='*60}")
print(" 所有步骤完成!")
print(f"{'='*60}")
print(" 可执行文件位置: dist/linux-storage-manager")
print(" 运行命令: sudo dist/linux-storage-manager")
sys.exit(0)
else:
print(f"\n{'='*60}")
print(" 打包过程中有步骤失败,请检查错误信息")
print(f"{'='*60}")
sys.exit(1)
if __name__ == "__main__":
main()