309 lines
9.5 KiB
Python
309 lines
9.5 KiB
Python
#!/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()
|