This commit is contained in:
zj
2026-02-09 17:59:50 +08:00
parent 1a3a4746a3
commit 0112e4d3b1
11 changed files with 3223 additions and 13 deletions

308
build.py Normal file
View File

@@ -0,0 +1,308 @@
#!/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()