|
| 1 | +# bootstrap.py 流程梳理与优化建议 |
| 2 | + |
| 3 | +## 一、基本流程梳理 |
| 4 | + |
| 5 | +### 1. 安装流程 (`install_from_tar`) |
| 6 | + |
| 7 | +``` |
| 8 | +main() |
| 9 | + └─> install_from_tar() |
| 10 | + ├─> resolve_tar_path() # 解析 tar 路径,可能需要下载 |
| 11 | + ├─> extract_tar() # 解压 tar 文件,获取所有 .whl 文件 |
| 12 | + ├─> filter_packages() # 过滤包(核心逻辑) |
| 13 | + │ ├─> get_package_name_from_whl() # 从 whl 文件名提取包名 |
| 14 | + │ ├─> _is_instrumentation_in_bootstrap_gen() # 检查是否为 instrumentation |
| 15 | + │ ├─> check_python_version_compatibility() # 检查 Python 版本兼容性 |
| 16 | + │ ├─> check_dependency_compatibility() # 检查依赖版本兼容性 |
| 17 | + │ └─> get_target_libraries_from_bootstrap_gen() + _is_library_installed() # 自动检测 |
| 18 | + └─> install_packages() # 使用 pip 安装 |
| 19 | +``` |
| 20 | + |
| 21 | +### 2. 卸载流程 (`uninstall_loongsuite_packages`) |
| 22 | + |
| 23 | +``` |
| 24 | +main() |
| 25 | + └─> uninstall_loongsuite_packages() |
| 26 | + ├─> get_installed_loongsuite_packages() # 获取已安装的包列表 |
| 27 | + └─> uninstall_packages() # 使用 pip 卸载 |
| 28 | +``` |
| 29 | + |
| 30 | +### 3. 核心辅助函数 |
| 31 | + |
| 32 | +- **包名处理**: |
| 33 | + - `get_package_name_from_whl()`: 从 whl 文件名提取包名 |
| 34 | + - `get_installed_package_version()`: 获取已安装包的版本(处理下划线/连字符变体) |
| 35 | + |
| 36 | +- **元数据提取**: |
| 37 | + - `get_metadata_from_whl()`: 从 whl 文件提取 METADATA |
| 38 | + - `get_python_requirement_from_whl()`: 提取 Python 版本要求 |
| 39 | + |
| 40 | +- **兼容性检查**: |
| 41 | + - `check_python_version_compatibility()`: 检查 Python 版本 |
| 42 | + - `check_dependency_compatibility()`: 检查依赖版本 |
| 43 | + |
| 44 | +- **bootstrap_gen 查询**: |
| 45 | + - `_is_instrumentation_in_bootstrap_gen()`: 检查是否为 instrumentation |
| 46 | + - `get_target_libraries_from_bootstrap_gen()`: 获取目标库列表 |
| 47 | + |
| 48 | +- **库检测**: |
| 49 | + - `_is_library_installed()`: 检查库是否已安装 |
| 50 | + |
| 51 | +## 二、发现的问题和优化建议 |
| 52 | + |
| 53 | +### 1. 🔴 包名规范化逻辑重复 |
| 54 | + |
| 55 | +**问题**: |
| 56 | +- `get_installed_package_version()` 中三次尝试(原始名、下划线→连字符、连字符→下划线) |
| 57 | +- `_is_library_installed()` 中也有类似逻辑 |
| 58 | +- 多个地方都有 `normalized_name = package_name.replace("_", "-")` 的重复 |
| 59 | + |
| 60 | +**建议**: |
| 61 | +```python |
| 62 | +def normalize_package_name(package_name: str) -> str: |
| 63 | + """统一规范化包名:将下划线转换为连字符""" |
| 64 | + return package_name.replace("_", "-") |
| 65 | + |
| 66 | +def get_package_name_variants(package_name: str) -> List[str]: |
| 67 | + """获取包名的所有可能变体(用于查找)""" |
| 68 | + normalized = normalize_package_name(package_name) |
| 69 | + variants = [package_name] |
| 70 | + if normalized != package_name: |
| 71 | + variants.append(normalized) |
| 72 | + # 如果需要,也可以添加反向变体 |
| 73 | + return variants |
| 74 | +``` |
| 75 | + |
| 76 | +### 2. 🔴 从 requirement 字符串提取包名的逻辑重复 |
| 77 | + |
| 78 | +**问题**: |
| 79 | +在 `_is_instrumentation_in_bootstrap_gen()` 和 `get_target_libraries_from_bootstrap_gen()` 中都有: |
| 80 | +```python |
| 81 | +default_pkg_name = ( |
| 82 | + default_instr.split("==")[0] |
| 83 | + .split(">=")[0] |
| 84 | + .split("<=")[0] |
| 85 | + .split("~=")[0] |
| 86 | + .split("!=")[0] |
| 87 | + .strip() |
| 88 | +) |
| 89 | +``` |
| 90 | + |
| 91 | +**建议**: |
| 92 | +```python |
| 93 | +def extract_package_name_from_requirement(req_str: str) -> str: |
| 94 | + """从 requirement 字符串中提取包名""" |
| 95 | + try: |
| 96 | + return Requirement(req_str).name |
| 97 | + except Exception: |
| 98 | + # Fallback: 手动解析 |
| 99 | + for op in ["==", ">=", "<=", "~=", "!=", ">", "<"]: |
| 100 | + if op in req_str: |
| 101 | + return req_str.split(op)[0].strip() |
| 102 | + return req_str.strip() |
| 103 | +``` |
| 104 | + |
| 105 | +### 3. 🟡 get_installed_package_version 中的重复代码 |
| 106 | + |
| 107 | +**问题**: |
| 108 | +三个几乎相同的 try-except 块,只是包名不同。 |
| 109 | + |
| 110 | +**建议**: |
| 111 | +```python |
| 112 | +def get_installed_package_version(package_name: str) -> Optional[str]: |
| 113 | + """获取已安装包的版本""" |
| 114 | + variants = get_package_name_variants(package_name) |
| 115 | + |
| 116 | + for variant in variants: |
| 117 | + version = _try_get_version(variant) |
| 118 | + if version: |
| 119 | + return version |
| 120 | + return None |
| 121 | + |
| 122 | +def _try_get_version(package_name: str) -> Optional[str]: |
| 123 | + """尝试获取单个包名变体的版本""" |
| 124 | + cmd = [sys.executable, "-m", "pip", "show", package_name] |
| 125 | + try: |
| 126 | + result = subprocess.run( |
| 127 | + cmd, capture_output=True, text=True, check=True, timeout=5 |
| 128 | + ) |
| 129 | + for line in result.stdout.splitlines(): |
| 130 | + if line.startswith("Version:"): |
| 131 | + return line.split(":", 1)[1].strip() |
| 132 | + except (subprocess.CalledProcessError, subprocess.TimeoutExpired): |
| 133 | + pass |
| 134 | + return None |
| 135 | +``` |
| 136 | + |
| 137 | +### 4. 🟡 filter_packages 函数过长 |
| 138 | + |
| 139 | +**问题**: |
| 140 | +`filter_packages()` 函数有 130+ 行,包含太多逻辑,可读性差。 |
| 141 | + |
| 142 | +**建议**: |
| 143 | +拆分为多个小函数: |
| 144 | +```python |
| 145 | +def filter_packages(...): |
| 146 | + """主函数,协调各个过滤步骤""" |
| 147 | + base_packages = [] |
| 148 | + instrumentation_packages = [] |
| 149 | + |
| 150 | + for whl_file in whl_files: |
| 151 | + package_name = get_package_name_from_whl(whl_file) |
| 152 | + |
| 153 | + if _should_skip_package(package_name, whl_file, blacklist, whitelist, |
| 154 | + skip_version_check, auto_detect): |
| 155 | + continue |
| 156 | + |
| 157 | + if package_name in BASE_DEPENDENCIES: |
| 158 | + base_packages.append(whl_file) |
| 159 | + else: |
| 160 | + if _should_install_instrumentation(package_name, whl_file, auto_detect): |
| 161 | + instrumentation_packages.append(whl_file) |
| 162 | + |
| 163 | + return base_packages, instrumentation_packages |
| 164 | + |
| 165 | +def _should_skip_package(...) -> bool: |
| 166 | + """检查是否应该跳过该包""" |
| 167 | + # 黑名单/白名单检查 |
| 168 | + # Python 版本检查 |
| 169 | + # 依赖版本检查 |
| 170 | + pass |
| 171 | + |
| 172 | +def _should_install_instrumentation(...) -> bool: |
| 173 | + """检查是否应该安装该 instrumentation""" |
| 174 | + # auto-detect 逻辑 |
| 175 | + pass |
| 176 | +``` |
| 177 | + |
| 178 | +### 5. 🟡 包名匹配逻辑重复 |
| 179 | + |
| 180 | +**问题**: |
| 181 | +多处都有 `normalized_name == package_name or default_pkg_name == package_name` 这样的匹配。 |
| 182 | + |
| 183 | +**建议**: |
| 184 | +```python |
| 185 | +def package_names_match(name1: str, name2: str) -> bool: |
| 186 | + """检查两个包名是否匹配(考虑规范化)""" |
| 187 | + normalized1 = normalize_package_name(name1) |
| 188 | + normalized2 = normalize_package_name(name2) |
| 189 | + return (normalized1 == normalized2 or |
| 190 | + name1 == name2 or |
| 191 | + normalized1 == name2 or |
| 192 | + name1 == normalized2) |
| 193 | +``` |
| 194 | + |
| 195 | +### 6. 🟢 常量提取 |
| 196 | + |
| 197 | +**问题**: |
| 198 | +`EXCLUDED_PACKAGES` 在函数内部定义,应该移到模块级别。 |
| 199 | + |
| 200 | +**建议**: |
| 201 | +```python |
| 202 | +# 在模块级别定义 |
| 203 | +UNINSTALL_EXCLUDED_PACKAGES = { |
| 204 | + "loongsuite-distro", |
| 205 | + "opentelemetry-api", |
| 206 | + "opentelemetry-sdk", |
| 207 | + "opentelemetry-instrumentation", |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +### 7. 🟢 错误处理改进 |
| 212 | + |
| 213 | +**问题**: |
| 214 | +多处使用 `except Exception: pass`,可能隐藏重要错误。 |
| 215 | + |
| 216 | +**建议**: |
| 217 | +更具体地捕获异常,至少记录警告: |
| 218 | +```python |
| 219 | +except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: |
| 220 | + logger.debug(f"Failed to get version for {package_name}: {e}") |
| 221 | + return None |
| 222 | +except Exception as e: |
| 223 | + logger.warning(f"Unexpected error getting version for {package_name}: {e}") |
| 224 | + return None |
| 225 | +``` |
| 226 | + |
| 227 | +### 8. 🟢 使用 packaging 库解析 requirement |
| 228 | + |
| 229 | +**问题**: |
| 230 | +手动解析 requirement 字符串(split("==")[0]...)不够健壮。 |
| 231 | + |
| 232 | +**建议**: |
| 233 | +统一使用 `packaging.requirements.Requirement` 解析(已经在用,但有些地方还在手动解析)。 |
| 234 | + |
| 235 | +### 9. 🟡 模块化建议 |
| 236 | + |
| 237 | +**建议**将代码拆分为多个模块**: |
| 238 | + |
| 239 | +``` |
| 240 | +loongsuite/distro/ |
| 241 | + ├── bootstrap.py # 主入口和 CLI |
| 242 | + ├── package_utils.py # 包名处理、版本获取等工具函数 |
| 243 | + ├── metadata.py # whl 元数据提取 |
| 244 | + ├── compatibility.py # 兼容性检查 |
| 245 | + └── bootstrap_gen.py # bootstrap_gen 查询(已存在) |
| 246 | +``` |
| 247 | + |
| 248 | +## 三、优先级建议 |
| 249 | + |
| 250 | +### 高优先级(立即优化) |
| 251 | +1. ✅ 提取包名规范化函数(减少重复,提高一致性) |
| 252 | +2. ✅ 提取 requirement 解析函数(多处使用,容易出错) |
| 253 | +3. ✅ 简化 `get_installed_package_version()`(消除重复代码) |
| 254 | + |
| 255 | +### 中优先级(后续优化) |
| 256 | +4. ⚠️ 拆分 `filter_packages()` 函数(提高可读性) |
| 257 | +5. ⚠️ 提取包名匹配函数(统一匹配逻辑) |
| 258 | +6. ⚠️ 改进错误处理(更好的调试体验) |
| 259 | + |
| 260 | +### 低优先级(可选) |
| 261 | +7. 💡 模块化拆分(如果文件继续增长) |
| 262 | +8. 💡 使用更专业的 metadata 解析库(如果遇到解析问题) |
| 263 | + |
| 264 | +## 四、总结 |
| 265 | + |
| 266 | +当前代码功能完整,但存在以下主要问题: |
| 267 | +1. **代码重复**:包名规范化、requirement 解析等逻辑在多处重复 |
| 268 | +2. **函数过长**:`filter_packages()` 函数包含太多逻辑 |
| 269 | +3. **错误处理**:过于宽泛的异常捕获可能隐藏问题 |
| 270 | + |
| 271 | +建议优先解决代码重复问题,这将提高代码的可维护性和一致性。 |
| 272 | + |
0 commit comments