#!/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()