#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 平台兼容性检测模块 自动检测操作系统和可用库,提供统一的兼容性接口 支持 Arch Linux (PySide6) 和 CentOS 8 (Tkinter) """ import sys import os import subprocess import platform import logging logger = logging.getLogger(__name__) class PlatformCompat: """平台兼容性检测类""" # GUI 后端类型 GUI_PYSIDE6 = "pyside6" GUI_TKINTER = "tkinter" GUI_NONE = "none" # 操作系统类型 OS_ARCH = "arch" OS_CENTOS = "centos" OS_RHEL = "rhel" OS_UBUNTU = "ubuntu" OS_DEBIAN = "debian" OS_UNKNOWN = "unknown" def __init__(self): self._gui_backend = None self._os_type = None self._os_version = None self._python_version = sys.version_info self._detected = False def detect(self): """执行平台检测""" if self._detected: return logger.info(f"Python版本: {self._python_version.major}.{self._python_version.minor}.{self._python_version.micro}") logger.info(f"平台: {platform.platform()}") logger.info(f"系统: {platform.system()}") logger.info(f"发行版: {platform.release()}") # 检测操作系统 self._detect_os() # 检测GUI后端 self._detect_gui_backend() self._detected = True logger.info(f"检测结果 - OS: {self._os_type}, GUI后端: {self._gui_backend}") def _detect_os(self): """检测操作系统类型""" try: # 尝试读取 /etc/os-release if os.path.exists('/etc/os-release'): with open('/etc/os-release', 'r') as f: content = f.read().lower() if 'arch' in content: self._os_type = self.OS_ARCH elif 'centos' in content: self._os_type = self.OS_CENTOS elif 'rhel' in content or 'red hat' in content: self._os_type = self.OS_RHEL elif 'ubuntu' in content: self._os_type = self.OS_UBUNTU elif 'debian' in content: self._os_type = self.OS_DEBIAN else: self._os_type = self.OS_UNKNOWN else: # 尝试使用 platform.linux_distribution() (Python 3.6 可用) distro = platform.dist() if hasattr(platform, 'dist') else (None, None, None) if distro[0]: distro_name = distro[0].lower() if 'centos' in distro_name or 'rhel' in distro_name: self._os_type = self.OS_CENTOS elif 'arch' in distro_name: self._os_type = self.OS_ARCH else: self._os_type = self.OS_UNKNOWN else: self._os_type = self.OS_UNKNOWN except Exception as e: logger.warning(f"检测操作系统失败: {e}") self._os_type = self.OS_UNKNOWN def _detect_gui_backend(self): """检测可用的GUI后端""" # 首先尝试 PySide6 (需要 Python 3.7+) if self._python_version >= (3, 7): try: import PySide6 self._gui_backend = self.GUI_PYSIDE6 logger.info("检测到 PySide6 可用") return except ImportError: logger.debug("PySide6 不可用") else: logger.debug(f"Python {self._python_version.major}.{self._python_version.minor} 不支持 PySide6 (需要 3.7+)") # 尝试 PySide2 (CentOS 8 可能可用) try: import PySide2 self._gui_backend = "pyside2" logger.info("检测到 PySide2 可用") return except ImportError: logger.debug("PySide2 不可用") # 最后尝试 Tkinter (Python 内置) try: import tkinter self._gui_backend = self.GUI_TKINTER logger.info("检测到 Tkinter 可用") return except ImportError: logger.debug("Tkinter 不可用") self._gui_backend = self.GUI_NONE logger.error("没有检测到可用的GUI后端") def get_gui_backend(self): """获取检测到的GUI后端""" if not self._detected: self.detect() return self._gui_backend def get_os_type(self): """获取检测到的操作系统类型""" if not self._detected: self.detect() return self._os_type def is_pyside6_available(self): """检查 PySide6 是否可用""" return self.get_gui_backend() == self.GUI_PYSIDE6 def is_tkinter_available(self): """检查 Tkinter 是否可用""" return self.get_gui_backend() == self.GUI_TKINTER def get_recommended_entry_point(self): """ 获取推荐的入口点模块名 统一使用 Tkinter 版本 返回: (模块名, 类名) """ return ("mainwindow_tkinter", "MainWindow") def get_system_command_compat(self): """ 获取系统命令兼容性配置 不同发行版的命令参数可能有差异 """ os_type = self.get_os_type() # 默认配置 config = { 'lsblk_json_available': True, # lsblk -J 支持 'parted_script_mode': True, # parted -s 支持 'lvm_json_report': True, # LVM JSON 报告格式 'mdadm_conf_paths': [ '/etc/mdadm/mdadm.conf', '/etc/mdadm.conf' ], } # CentOS/RHEL 8 特定配置 if os_type in (self.OS_CENTOS, self.OS_RHEL): # CentOS 8 的 lsblk 支持 -J,但某些旧版本可能有问题 # 检查实际版本 try: result = subprocess.run(['lsblk', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) version_output = result.stdout.decode('utf-8', errors='ignore') + result.stderr.decode('utf-8', errors='ignore') # util-linux 2.23+ 支持 JSON if '2.23' in version_output or '2.3' in version_output: config['lsblk_json_available'] = True else: # 尝试解析版本号 import re version_match = re.search(r'(\d+)\.(\d+)', version_output) if version_match: major = int(version_match.group(1)) minor = int(version_match.group(2)) config['lsblk_json_available'] = (major > 2) or (major == 2 and minor >= 23) except Exception: pass # CentOS 8 的 LVM 版本支持 JSON 报告 try: result = subprocess.run(['lvs', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # LVM 2.2.02.166+ 支持 JSON version_output = result.stdout.decode('utf-8', errors='ignore') + result.stderr.decode('utf-8', errors='ignore') import re version_match = re.search(r'(\d+)\.(\d+)\.(\d+)\.(\d+)', version_output) if version_match: major = int(version_match.group(1)) minor = int(version_match.group(2)) config['lvm_json_report'] = (major >= 2) and (minor >= 2) except Exception: pass return config def get_installation_guide(self): """获取当前系统的安装指南""" os_type = self.get_os_type() backend = self.get_gui_backend() guides = [] if os_type == self.OS_CENTOS: guides.append("CentOS 8 安装命令:") guides.append(" sudo yum install -y python3 python3-tkinter parted mdadm lvm2") guides.append(" sudo yum install -y dosfstools e2fsprogs xfsprogs ntfs-3g") if backend == self.GUI_TKINTER: guides.append(" sudo pip3 install pexpect") elif os_type == self.OS_ARCH: guides.append("Arch Linux 安装命令:") guides.append(" sudo pacman -S python python-pyside6 parted mdadm lvm2") guides.append(" sudo pacman -S dosfstools e2fsprogs xfsprogs ntfs-3g") guides.append(" pip install pexpect") else: guides.append("通用安装命令:") guides.append(" 请安装: python3, tkinter, parted, mdadm, lvm2") guides.append(" 以及文件系统工具: e2fsprogs, xfsprogs, dosfstools, ntfs-3g") return "\n".join(guides) # 全局实例 _platform_compat = None def get_platform_compat(): """获取全局平台兼容性实例""" global _platform_compat if _platform_compat is None: _platform_compat = PlatformCompat() _platform_compat.detect() return _platform_compat def check_prerequisites(): """ 检查系统前提条件 返回: (是否通过, 错误信息列表) """ compat = get_platform_compat() errors = [] warnings = [] # 检查 GUI 后端 if compat.get_gui_backend() == PlatformCompat.GUI_NONE: errors.append("没有检测到可用的GUI后端 (PySide6/PySide2/Tkinter)") # 检查必要的系统命令 required_commands = [ ('lsblk', '用于获取块设备信息'), ('parted', '用于分区操作'), ('mkfs.ext4', '用于创建ext4文件系统'), ('mount', '用于挂载操作'), ] optional_commands = [ ('mdadm', '用于RAID管理'), ('pvcreate', '用于LVM物理卷管理'), ('vgcreate', '用于LVM卷组管理'), ('lvcreate', '用于LVM逻辑卷管理'), ('mkfs.xfs', '用于创建XFS文件系统'), ('mkfs.ntfs', '用于创建NTFS文件系统'), ('mkfs.vfat', '用于创建FAT32文件系统'), ] for cmd, desc in required_commands: if not _command_exists(cmd): errors.append(f"缺少必要命令: {cmd} ({desc})") for cmd, desc in optional_commands: if not _command_exists(cmd): warnings.append(f"缺少可选命令: {cmd} ({desc})") # 检查权限 if os.geteuid() != 0: warnings.append("当前不是root用户,部分功能可能需要sudo权限") return (len(errors) == 0, errors, warnings) def _command_exists(cmd): """检查命令是否存在""" try: # Python 3.6 兼容: 使用 stdout/stderr 参数代替 capture_output result = subprocess.run( ['which', cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True ) return True except (subprocess.CalledProcessError, FileNotFoundError): return False if __name__ == "__main__": # 测试模式 logging.basicConfig(level=logging.DEBUG) compat = get_platform_compat() print(f"操作系统类型: {compat.get_os_type()}") print(f"GUI后端: {compat.get_gui_backend()}") print(f"推荐入口点: {compat.get_recommended_entry_point()}") print(f"系统命令兼容配置: {compat.get_system_command_compat()}") print("\n" + compat.get_installation_guide()) print("\n前提条件检查:") passed, errors, warnings = check_prerequisites() if passed: print("✓ 基本前提条件满足") else: print("✗ 前提条件不满足:") for e in errors: print(f" 错误: {e}") for w in warnings: print(f" 警告: {w}")