gitinstall 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitinstall/__init__.py +61 -0
- gitinstall/_sdk.py +541 -0
- gitinstall/academic.py +831 -0
- gitinstall/admin.html +327 -0
- gitinstall/auto_update.py +384 -0
- gitinstall/autopilot.py +349 -0
- gitinstall/badge.py +476 -0
- gitinstall/checkpoint.py +330 -0
- gitinstall/cicd.py +499 -0
- gitinstall/clawhub.html +718 -0
- gitinstall/config_schema.py +353 -0
- gitinstall/db.py +984 -0
- gitinstall/db_backend.py +445 -0
- gitinstall/dep_chain.py +337 -0
- gitinstall/dependency_audit.py +1153 -0
- gitinstall/detector.py +542 -0
- gitinstall/doctor.py +493 -0
- gitinstall/education.py +869 -0
- gitinstall/enterprise.py +802 -0
- gitinstall/error_fixer.py +953 -0
- gitinstall/event_bus.py +251 -0
- gitinstall/executor.py +577 -0
- gitinstall/feature_flags.py +138 -0
- gitinstall/fetcher.py +921 -0
- gitinstall/huggingface.py +922 -0
- gitinstall/hw_detect.py +988 -0
- gitinstall/i18n.py +664 -0
- gitinstall/installer_registry.py +362 -0
- gitinstall/knowledge_base.py +379 -0
- gitinstall/license_check.py +605 -0
- gitinstall/llm.py +569 -0
- gitinstall/log.py +236 -0
- gitinstall/main.py +1408 -0
- gitinstall/mcp_agent.py +841 -0
- gitinstall/mcp_server.py +386 -0
- gitinstall/monorepo.py +810 -0
- gitinstall/multi_source.py +425 -0
- gitinstall/onboard.py +276 -0
- gitinstall/planner.py +222 -0
- gitinstall/planner_helpers.py +323 -0
- gitinstall/planner_known_projects.py +1010 -0
- gitinstall/planner_templates.py +996 -0
- gitinstall/remote_gpu.py +633 -0
- gitinstall/resilience.py +608 -0
- gitinstall/run_tests.py +572 -0
- gitinstall/skills.py +476 -0
- gitinstall/tool_schemas.py +324 -0
- gitinstall/trending.py +279 -0
- gitinstall/uninstaller.py +415 -0
- gitinstall/validate_top100.py +607 -0
- gitinstall/watchdog.py +180 -0
- gitinstall/web.py +1277 -0
- gitinstall/web_ui.html +2277 -0
- gitinstall-1.1.0.dist-info/METADATA +275 -0
- gitinstall-1.1.0.dist-info/RECORD +59 -0
- gitinstall-1.1.0.dist-info/WHEEL +5 -0
- gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
- gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
- gitinstall-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
"""
|
|
2
|
+
error_fixer.py — 无需 LLM 的规则化错误自动修复引擎
|
|
3
|
+
====================================================
|
|
4
|
+
|
|
5
|
+
覆盖安装场景 90%+ 的常见报错:
|
|
6
|
+
- 缺依赖(python/node/rust/go 等未安装)
|
|
7
|
+
- 权限不足(pip 需要 --user)
|
|
8
|
+
- 端口占用 / 网络问题
|
|
9
|
+
- 虚拟环境激活失败
|
|
10
|
+
- Python 版本不匹配
|
|
11
|
+
- npm 审计 / peer dependency 问题
|
|
12
|
+
- 编译缺依赖(cmake/gcc/make 等)
|
|
13
|
+
|
|
14
|
+
返回 FixSuggestion 供 executor 执行,无需任何 API Key 或模型。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import platform
|
|
21
|
+
import re
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class FixSuggestion:
|
|
27
|
+
"""一个修复建议"""
|
|
28
|
+
root_cause: str # 根本原因(一句话)
|
|
29
|
+
fix_commands: list[str] # 修复命令列表
|
|
30
|
+
retry_original: bool # 修复后是否重试原命令
|
|
31
|
+
confidence: str # high / medium / low
|
|
32
|
+
outcome: str = "fixed" # fixed / trusted_failure
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ─── macOS / Linux / Windows 包管理器检测 ───
|
|
36
|
+
|
|
37
|
+
def _is_macos() -> bool:
|
|
38
|
+
return platform.system() == "Darwin"
|
|
39
|
+
|
|
40
|
+
def _is_linux() -> bool:
|
|
41
|
+
return platform.system() == "Linux"
|
|
42
|
+
|
|
43
|
+
def _has_brew() -> bool:
|
|
44
|
+
return os.path.exists("/opt/homebrew/bin/brew") or os.path.exists("/usr/local/bin/brew")
|
|
45
|
+
|
|
46
|
+
def _has_apt() -> bool:
|
|
47
|
+
return os.path.exists("/usr/bin/apt-get")
|
|
48
|
+
|
|
49
|
+
def _install_pkg_cmd(pkg: str, brew_pkg: str = "", apt_pkg: str = "") -> str:
|
|
50
|
+
"""根据平台返回安装系统包的命令"""
|
|
51
|
+
if _is_macos() and _has_brew():
|
|
52
|
+
return f"brew install {brew_pkg or pkg}"
|
|
53
|
+
if _is_linux() and _has_apt():
|
|
54
|
+
return f"sudo apt-get install -y {apt_pkg or pkg}"
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _haskell_macos_env_exports(formulas: list[str]) -> str:
|
|
59
|
+
lines = [
|
|
60
|
+
'BREW_PREFIX="$(brew --prefix)"',
|
|
61
|
+
'export PKG_CONFIG_PATH="$BREW_PREFIX/lib/pkgconfig:$BREW_PREFIX/share/pkgconfig:${PKG_CONFIG_PATH:-}"',
|
|
62
|
+
'export CPATH="$BREW_PREFIX/include:${CPATH:-}"',
|
|
63
|
+
'export LIBRARY_PATH="$BREW_PREFIX/lib:${LIBRARY_PATH:-}"',
|
|
64
|
+
'export PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1',
|
|
65
|
+
]
|
|
66
|
+
for formula in formulas:
|
|
67
|
+
lines.append(
|
|
68
|
+
f'if brew --prefix {formula} >/dev/null 2>&1; then FORMULA_PREFIX="$(brew --prefix {formula})"; '
|
|
69
|
+
'export PKG_CONFIG_PATH="$FORMULA_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH"; '
|
|
70
|
+
'export CPATH="$FORMULA_PREFIX/include:$CPATH"; '
|
|
71
|
+
'export LIBRARY_PATH="$FORMULA_PREFIX/lib:$LIBRARY_PATH"; fi'
|
|
72
|
+
)
|
|
73
|
+
return "; ".join(lines)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ─── 错误模式规则库 ───────────────────────────
|
|
77
|
+
|
|
78
|
+
# 每条规则:(stderr/stdout 正则, 生成 fix 的函数)
|
|
79
|
+
# 函数签名: (command, stderr, stdout) -> Optional[FixSuggestion]
|
|
80
|
+
|
|
81
|
+
def _fix_command_not_found(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
82
|
+
"""命令未找到:自动安装缺失工具"""
|
|
83
|
+
# "command not found", "not recognized", "No such file"
|
|
84
|
+
patterns = [
|
|
85
|
+
(r'(?:command not found|not found):\s*(\w[\w.-]*)', 1),
|
|
86
|
+
(r"(\w[\w.-]*):\s*command not found", 1),
|
|
87
|
+
(r"'(\w[\w.-]*)' is not recognized", 1),
|
|
88
|
+
(r'No such file or directory.*?[/\\](\w+)', 1),
|
|
89
|
+
(r'which:\s+no\s+(\w+)', 1),
|
|
90
|
+
]
|
|
91
|
+
combined = stderr + "\n" + stdout
|
|
92
|
+
for pattern, group in patterns:
|
|
93
|
+
m = re.search(pattern, combined, re.IGNORECASE)
|
|
94
|
+
if m:
|
|
95
|
+
missing = m.group(group).lower()
|
|
96
|
+
return _suggest_install_tool(missing, cmd)
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _suggest_install_tool(tool: str, original_cmd: str) -> FixSuggestion | None:
|
|
101
|
+
"""为缺失的工具生成安装命令"""
|
|
102
|
+
# 工具 → (brew_name, apt_name, description)
|
|
103
|
+
TOOL_MAP = {
|
|
104
|
+
"python3": ("python@3.12", "python3", "Python 3"),
|
|
105
|
+
"python": ("python@3.12", "python3", "Python 3"),
|
|
106
|
+
"pip": ("python@3.12", "python3-pip", "pip"),
|
|
107
|
+
"pip3": ("python@3.12", "python3-pip", "pip3"),
|
|
108
|
+
"node": ("node", "nodejs", "Node.js"),
|
|
109
|
+
"npm": ("node", "npm", "npm"),
|
|
110
|
+
"npx": ("node", "npm", "npx"),
|
|
111
|
+
"yarn": ("yarn", "yarn", "yarn"),
|
|
112
|
+
"pnpm": ("pnpm", "pnpm", "pnpm"),
|
|
113
|
+
"cargo": ("rust", "cargo", "Rust/Cargo"),
|
|
114
|
+
"rustc": ("rust", "rustc", "Rust"),
|
|
115
|
+
"rustup": ("rustup", "rustup", "rustup"),
|
|
116
|
+
"go": ("go", "golang", "Go"),
|
|
117
|
+
"java": ("openjdk", "default-jdk", "Java JDK"),
|
|
118
|
+
"javac": ("openjdk", "default-jdk", "Java JDK"),
|
|
119
|
+
"mvn": ("maven", "maven", "Maven"),
|
|
120
|
+
"gradle": ("gradle", "gradle", "Gradle"),
|
|
121
|
+
"cmake": ("cmake", "cmake", "CMake"),
|
|
122
|
+
"make": ("make", "build-essential", "make/gcc"),
|
|
123
|
+
"gcc": ("gcc", "build-essential", "GCC"),
|
|
124
|
+
"g++": ("gcc", "build-essential", "G++"),
|
|
125
|
+
"git": ("git", "git", "Git"),
|
|
126
|
+
"curl": ("curl", "curl", "curl"),
|
|
127
|
+
"wget": ("wget", "wget", "wget"),
|
|
128
|
+
"docker": ("docker", "docker.io", "Docker"),
|
|
129
|
+
"ruby": ("ruby", "ruby-full", "Ruby"),
|
|
130
|
+
"gem": ("ruby", "ruby-full", "RubyGems"),
|
|
131
|
+
"bundle": ("ruby", "ruby-bundler", "Bundler"),
|
|
132
|
+
"php": ("php", "php", "PHP"),
|
|
133
|
+
"composer": ("composer", "composer", "Composer"),
|
|
134
|
+
"swift": ("swift", "", "Swift"),
|
|
135
|
+
"mix": ("elixir", "elixir", "Elixir"),
|
|
136
|
+
"stack": ("haskell-stack", "haskell-stack", "Haskell Stack"),
|
|
137
|
+
"sbt": ("sbt", "", "SBT"),
|
|
138
|
+
"zig": ("zig", "", "Zig"),
|
|
139
|
+
"lein": ("leiningen", "leiningen", "Leiningen"),
|
|
140
|
+
"uv": ("uv", "", "uv"),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if tool in TOOL_MAP:
|
|
144
|
+
brew_name, apt_name, desc = TOOL_MAP[tool]
|
|
145
|
+
install_cmd = _install_pkg_cmd(tool, brew_name, apt_name)
|
|
146
|
+
if install_cmd:
|
|
147
|
+
return FixSuggestion(
|
|
148
|
+
root_cause=f"{desc} 未安装",
|
|
149
|
+
fix_commands=[install_cmd],
|
|
150
|
+
retry_original=True,
|
|
151
|
+
confidence="high",
|
|
152
|
+
)
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _fix_pip_permission(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
157
|
+
"""pip 权限问题 → 加 --user 或用 venv"""
|
|
158
|
+
if not re.search(r'pip3?\s+install', cmd, re.IGNORECASE):
|
|
159
|
+
return None
|
|
160
|
+
if re.search(r'Permission denied|Could not install packages.*user|externally-managed-environment', stderr, re.IGNORECASE):
|
|
161
|
+
# PEP 668 externally-managed-environment(macOS Sonoma+, Ubuntu 23.04+)
|
|
162
|
+
if "externally-managed-environment" in stderr:
|
|
163
|
+
return FixSuggestion(
|
|
164
|
+
root_cause="系统 Python 受保护(PEP 668),需使用虚拟环境",
|
|
165
|
+
fix_commands=[
|
|
166
|
+
"python3 -m venv venv",
|
|
167
|
+
"source venv/bin/activate",
|
|
168
|
+
],
|
|
169
|
+
retry_original=True,
|
|
170
|
+
confidence="high",
|
|
171
|
+
)
|
|
172
|
+
# 普通权限问题 → --user
|
|
173
|
+
new_cmd = cmd.rstrip() + " --user"
|
|
174
|
+
return FixSuggestion(
|
|
175
|
+
root_cause="pip 安装权限不足",
|
|
176
|
+
fix_commands=[new_cmd],
|
|
177
|
+
retry_original=False, # fix_commands 本身就是替代原命令
|
|
178
|
+
confidence="high",
|
|
179
|
+
)
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _fix_pip_not_found_package(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
184
|
+
"""pip 找不到包 → 检查包名拼写或提示 extras"""
|
|
185
|
+
if not re.search(r'pip3?\s+install', cmd, re.IGNORECASE):
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
# "No matching distribution found for xxx"
|
|
189
|
+
m = re.search(r'No matching distribution found for (\S+)', stderr)
|
|
190
|
+
if m:
|
|
191
|
+
pkg = m.group(1)
|
|
192
|
+
# 常见拼写错误映射
|
|
193
|
+
typos = {
|
|
194
|
+
"sklearn": "scikit-learn",
|
|
195
|
+
"cv2": "opencv-python",
|
|
196
|
+
"PIL": "Pillow",
|
|
197
|
+
"yaml": "PyYAML",
|
|
198
|
+
"dotenv": "python-dotenv",
|
|
199
|
+
"attr": "attrs",
|
|
200
|
+
}
|
|
201
|
+
if pkg in typos:
|
|
202
|
+
new_cmd = cmd.replace(pkg, typos[pkg])
|
|
203
|
+
return FixSuggestion(
|
|
204
|
+
root_cause=f"包名 '{pkg}' 不正确,应为 '{typos[pkg]}'",
|
|
205
|
+
fix_commands=[new_cmd],
|
|
206
|
+
retry_original=False,
|
|
207
|
+
confidence="high",
|
|
208
|
+
)
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _fix_npm_permission(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
213
|
+
"""npm EACCES / permission denied → 根据具体场景修复"""
|
|
214
|
+
if not re.search(r'\bnpm\b', cmd, re.IGNORECASE):
|
|
215
|
+
return None
|
|
216
|
+
if not re.search(r'EACCES|permission denied', stderr, re.IGNORECASE):
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
# npm 缓存目录的 root 文件(历史遗留问题)
|
|
220
|
+
if re.search(r'cache folder contains root-owned files|\.npm/_cacache', stderr):
|
|
221
|
+
return FixSuggestion(
|
|
222
|
+
root_cause="npm 缓存目录权限异常,清理缓存后重试",
|
|
223
|
+
fix_commands=["npm cache clean --force"],
|
|
224
|
+
retry_original=True,
|
|
225
|
+
confidence="high",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# 全局安装权限问题
|
|
229
|
+
if re.search(r'-g\b|--global', cmd):
|
|
230
|
+
return FixSuggestion(
|
|
231
|
+
root_cause="npm 全局安装权限不足",
|
|
232
|
+
fix_commands=["npm config set prefix ~/.npm-global"],
|
|
233
|
+
retry_original=True,
|
|
234
|
+
confidence="high",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# 本地安装权限问题
|
|
238
|
+
return FixSuggestion(
|
|
239
|
+
root_cause="npm 安装目录权限不足,清理 node_modules 重试",
|
|
240
|
+
fix_commands=["rm -rf node_modules package-lock.json"],
|
|
241
|
+
retry_original=True,
|
|
242
|
+
confidence="medium",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _fix_npm_audit(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
247
|
+
"""npm install 有 audit 问题但实际成功"""
|
|
248
|
+
if not re.search(r'npm\s+install', cmd, re.IGNORECASE):
|
|
249
|
+
return None
|
|
250
|
+
# npm install 报 audit 漏洞但 exit code != 0
|
|
251
|
+
combined = stderr + stdout
|
|
252
|
+
if re.search(r'added \d+ packages', combined) and re.search(r'vulnerabilities', combined):
|
|
253
|
+
return FixSuggestion(
|
|
254
|
+
root_cause="npm 安装成功但有安全审计警告(可忽略)",
|
|
255
|
+
fix_commands=[],
|
|
256
|
+
retry_original=False,
|
|
257
|
+
confidence="high",
|
|
258
|
+
)
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _fix_npm_workspace_protocol(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
263
|
+
"""npm 不支持 workspace: 协议 → 切换 pnpm"""
|
|
264
|
+
if not re.search(r'\bnpm\b', cmd, re.IGNORECASE):
|
|
265
|
+
return None
|
|
266
|
+
combined = stderr + stdout
|
|
267
|
+
if re.search(r'EUNSUPPORTEDPROTOCOL.*workspace:', combined, re.IGNORECASE):
|
|
268
|
+
# 项目使用 pnpm/yarn workspace 协议,npm 不支持
|
|
269
|
+
new_cmd = cmd.replace("npm install", "pnpm install").replace("npm i", "pnpm install")
|
|
270
|
+
return FixSuggestion(
|
|
271
|
+
root_cause="项目使用 workspace 协议,需 pnpm 而非 npm",
|
|
272
|
+
fix_commands=[new_cmd] if new_cmd != cmd else ["pnpm install"],
|
|
273
|
+
retry_original=False,
|
|
274
|
+
confidence="high",
|
|
275
|
+
)
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _fix_npm_eexist(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
280
|
+
"""npm EEXIST 文件冲突 → 清理缓存重试"""
|
|
281
|
+
if not re.search(r'\bnpm\b', cmd, re.IGNORECASE):
|
|
282
|
+
return None
|
|
283
|
+
if re.search(r'EEXIST', stderr):
|
|
284
|
+
return FixSuggestion(
|
|
285
|
+
root_cause="npm 缓存文件冲突",
|
|
286
|
+
fix_commands=["npm cache clean --force"],
|
|
287
|
+
retry_original=True,
|
|
288
|
+
confidence="medium",
|
|
289
|
+
)
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _fix_node_version(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
294
|
+
"""Node.js 版本过低"""
|
|
295
|
+
combined = stderr + stdout
|
|
296
|
+
m = re.search(r'(?:engine|requires|need)\s*node\s*[><=]+\s*([\d.]+)', combined, re.IGNORECASE)
|
|
297
|
+
if m:
|
|
298
|
+
required_ver = m.group(1)
|
|
299
|
+
return FixSuggestion(
|
|
300
|
+
root_cause=f"Node.js 版本过低,需要 >= {required_ver}",
|
|
301
|
+
fix_commands=[_install_pkg_cmd("node", "node", "nodejs") or "brew install node"],
|
|
302
|
+
retry_original=True,
|
|
303
|
+
confidence="medium",
|
|
304
|
+
)
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _fix_python_version(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
309
|
+
"""Python 版本不匹配"""
|
|
310
|
+
combined = stderr + stdout
|
|
311
|
+
m = re.search(r'python_requires\s*[><=]+\s*"?([\d.]+)', combined, re.IGNORECASE)
|
|
312
|
+
if not m:
|
|
313
|
+
m = re.search(r'requires\s+(?:a\s+different\s+)?Python\s*[><=]+\s*([\d.]+)', combined, re.IGNORECASE)
|
|
314
|
+
if m:
|
|
315
|
+
required_ver = m.group(1)
|
|
316
|
+
major_minor = required_ver.rsplit(".", 1)[0] if "." in required_ver else required_ver
|
|
317
|
+
return FixSuggestion(
|
|
318
|
+
root_cause=f"Python 版本过低,需要 >= {required_ver}",
|
|
319
|
+
fix_commands=[_install_pkg_cmd("python", f"python@{major_minor}", f"python{major_minor}")
|
|
320
|
+
or f"brew install python@{major_minor}"],
|
|
321
|
+
retry_original=True,
|
|
322
|
+
confidence="medium",
|
|
323
|
+
)
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _fix_rust_compile_error(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
328
|
+
"""Rust/Cargo 编译依赖缺失"""
|
|
329
|
+
if not re.search(r'\bcargo\b', cmd, re.IGNORECASE):
|
|
330
|
+
return None
|
|
331
|
+
combined = stderr + stdout
|
|
332
|
+
# "linker 'cc' not found" or "failed to run custom build command"
|
|
333
|
+
if re.search(r"linker.*not found|can't find cc", combined, re.IGNORECASE):
|
|
334
|
+
install_cmd = _install_pkg_cmd("gcc", "gcc", "build-essential")
|
|
335
|
+
if install_cmd:
|
|
336
|
+
return FixSuggestion(
|
|
337
|
+
root_cause="C 编译器未安装(Rust 链接需要)",
|
|
338
|
+
fix_commands=[install_cmd],
|
|
339
|
+
retry_original=True,
|
|
340
|
+
confidence="high",
|
|
341
|
+
)
|
|
342
|
+
# OpenSSL 缺失
|
|
343
|
+
if re.search(r'openssl.*not found|Could not find directory of OpenSSL', combined, re.IGNORECASE):
|
|
344
|
+
install_cmd = _install_pkg_cmd("openssl", "openssl", "libssl-dev")
|
|
345
|
+
if install_cmd:
|
|
346
|
+
return FixSuggestion(
|
|
347
|
+
root_cause="OpenSSL 开发库未安装",
|
|
348
|
+
fix_commands=[install_cmd],
|
|
349
|
+
retry_original=True,
|
|
350
|
+
confidence="high",
|
|
351
|
+
)
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _fix_cargo_git_install_layout(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
356
|
+
"""cargo install --git 遇到 workspace/库仓库时给出更合适的替代命令。"""
|
|
357
|
+
url_match = re.search(r'cargo\s+install\s+--git\s+(https?://\S+)', cmd, re.IGNORECASE)
|
|
358
|
+
if not url_match:
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
combined = stderr + stdout
|
|
362
|
+
repo_url = url_match.group(1).rstrip()
|
|
363
|
+
repo_name = repo_url.rstrip('/').rsplit('/', 1)[-1].removesuffix('.git')
|
|
364
|
+
|
|
365
|
+
multiple = re.search(r'multiple packages with binaries found:\s*([^\.]+)', combined, re.IGNORECASE)
|
|
366
|
+
if multiple:
|
|
367
|
+
packages = [part.strip() for part in multiple.group(1).split(',') if part.strip()]
|
|
368
|
+
target_pkg = None
|
|
369
|
+
for pkg in packages:
|
|
370
|
+
if pkg == repo_name or pkg == repo_name.replace('_', '-'):
|
|
371
|
+
target_pkg = pkg
|
|
372
|
+
break
|
|
373
|
+
if target_pkg:
|
|
374
|
+
return FixSuggestion(
|
|
375
|
+
root_cause="Rust workspace 含多个可执行包,需要指定 package",
|
|
376
|
+
fix_commands=[f"cargo install --git {repo_url} --package {target_pkg}"],
|
|
377
|
+
retry_original=False,
|
|
378
|
+
confidence="high",
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if re.search(r'no packages found with binarie', combined, re.IGNORECASE):
|
|
382
|
+
return FixSuggestion(
|
|
383
|
+
root_cause="该 Rust 仓库不是可直接 cargo install 的 CLI,回退到源码/Python 包安装",
|
|
384
|
+
fix_commands=[
|
|
385
|
+
f"git clone --depth 1 {repo_url}.git 2>/dev/null || git clone --depth 1 {repo_url}",
|
|
386
|
+
f"cd {repo_name}",
|
|
387
|
+
"test -f pyproject.toml && (python3 -m venv venv && source venv/bin/activate && pip install -e .) || cargo build --release",
|
|
388
|
+
],
|
|
389
|
+
retry_original=False,
|
|
390
|
+
confidence="medium",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _fix_go_no_root_module(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
397
|
+
"""go build ./... 在无根 go.mod 的仓库中失败(monorepo 或旧项目)"""
|
|
398
|
+
if "go build" not in cmd and "go install" not in cmd:
|
|
399
|
+
return None
|
|
400
|
+
combined = stderr + stdout
|
|
401
|
+
if ("does not contain main module" not in combined
|
|
402
|
+
and "go.mod file not found" not in combined
|
|
403
|
+
and "cannot find module providing" not in combined):
|
|
404
|
+
return None
|
|
405
|
+
return FixSuggestion(
|
|
406
|
+
root_cause="仓库根目录没有 go.mod,可能是 Go monorepo 或旧 GOPATH 项目",
|
|
407
|
+
fix_commands=[
|
|
408
|
+
"find . -name go.mod -maxdepth 3 -not -path '*/vendor/*' "
|
|
409
|
+
"-exec dirname {} \\; | head -5 | "
|
|
410
|
+
"while read d; do echo \"Building $d\"; (cd \"$d\" && go build ./...); done"
|
|
411
|
+
],
|
|
412
|
+
retry_original=False,
|
|
413
|
+
confidence="medium",
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _fix_pip_build_wheel(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
418
|
+
"""pip install 时 C 扩展构建 wheel 失败"""
|
|
419
|
+
if "pip install" not in cmd and "pip3 install" not in cmd:
|
|
420
|
+
return None
|
|
421
|
+
combined = stderr + stdout
|
|
422
|
+
if not re.search(r'Getting requirements to build wheel did not run successfully|'
|
|
423
|
+
r'Failed building wheel for|'
|
|
424
|
+
r'error: subprocess-exited-with-error.*build', combined, re.DOTALL):
|
|
425
|
+
return None
|
|
426
|
+
# 提取失败的包名
|
|
427
|
+
pkg_match = re.search(r'(?:Failed building wheel for|error:.*for )(\S+)', combined)
|
|
428
|
+
pkg_name = pkg_match.group(1).strip("'\"") if pkg_match else "某些包"
|
|
429
|
+
return FixSuggestion(
|
|
430
|
+
root_cause=f"{pkg_name} 的 C 扩展编译失败,尝试仅安装预编译包或跳过有问题的依赖",
|
|
431
|
+
fix_commands=[
|
|
432
|
+
cmd + " --only-binary :all: --ignore-installed",
|
|
433
|
+
],
|
|
434
|
+
retry_original=False,
|
|
435
|
+
confidence="medium",
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _fix_cmake_not_found(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
440
|
+
"""CMake 编译缺依赖"""
|
|
441
|
+
combined = stderr + stdout
|
|
442
|
+
if re.search(r'cmake.*not found|CMake.*is required', combined, re.IGNORECASE):
|
|
443
|
+
install_cmd = _install_pkg_cmd("cmake", "cmake", "cmake")
|
|
444
|
+
if install_cmd:
|
|
445
|
+
return FixSuggestion(
|
|
446
|
+
root_cause="CMake 未安装",
|
|
447
|
+
fix_commands=[install_cmd],
|
|
448
|
+
retry_original=True,
|
|
449
|
+
confidence="high",
|
|
450
|
+
)
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _fix_build_essentials(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
455
|
+
"""编译缺工具链(gcc/make/pkg-config)"""
|
|
456
|
+
combined = stderr + stdout
|
|
457
|
+
# "gcc: command not found" / "make: command not found"
|
|
458
|
+
for tool in ("gcc", "g++", "make", "cc"):
|
|
459
|
+
if re.search(rf'\b{tool}\b.*(?:not found|No such file)', combined, re.IGNORECASE):
|
|
460
|
+
if _is_macos():
|
|
461
|
+
return FixSuggestion(
|
|
462
|
+
root_cause=f"编译工具链未安装({tool})",
|
|
463
|
+
fix_commands=["xcode-select --install"],
|
|
464
|
+
retry_original=True,
|
|
465
|
+
confidence="high",
|
|
466
|
+
)
|
|
467
|
+
if _is_linux() and _has_apt():
|
|
468
|
+
return FixSuggestion(
|
|
469
|
+
root_cause=f"编译工具链未安装({tool})",
|
|
470
|
+
fix_commands=["sudo apt-get install -y build-essential"],
|
|
471
|
+
retry_original=True,
|
|
472
|
+
confidence="high",
|
|
473
|
+
)
|
|
474
|
+
# pkg-config(排除 cabal/stack 的依赖解析错误,那些由 Haskell 专用规则处理)
|
|
475
|
+
if (re.search(r'pkg-config.*not found(?! in the pkg-config database)', combined, re.IGNORECASE)
|
|
476
|
+
and not re.search(r'\bcabal\b|\bstack\b', cmd, re.IGNORECASE)):
|
|
477
|
+
install_cmd = _install_pkg_cmd("pkg-config", "pkg-config", "pkg-config")
|
|
478
|
+
if install_cmd:
|
|
479
|
+
return FixSuggestion(
|
|
480
|
+
root_cause="pkg-config 未安装",
|
|
481
|
+
fix_commands=[install_cmd],
|
|
482
|
+
retry_original=True,
|
|
483
|
+
confidence="high",
|
|
484
|
+
)
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _fix_port_in_use(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
489
|
+
"""端口占用"""
|
|
490
|
+
combined = stderr + stdout
|
|
491
|
+
# EADDRINUSE :::3000 / address already in use / port 8080 in use
|
|
492
|
+
m = re.search(r'(?:EADDRINUSE[^\d]*(\d+)|address already in use[^\d]*(\d+)|port\s+(\d+).*(?:in use|occupied))', combined, re.IGNORECASE)
|
|
493
|
+
if not m:
|
|
494
|
+
# "Address already in use" without port number — try to extract from command
|
|
495
|
+
if re.search(r'address already in use', combined, re.IGNORECASE):
|
|
496
|
+
# Try extracting port from the command itself
|
|
497
|
+
port_m = re.search(r'(?:-p\s*|--port[= ]\s*|:)(\d{2,5})\b', cmd)
|
|
498
|
+
if not port_m:
|
|
499
|
+
port_m = re.search(r'\b(\d{4,5})\b', cmd)
|
|
500
|
+
port = port_m.group(1) if port_m else "8080"
|
|
501
|
+
return FixSuggestion(
|
|
502
|
+
root_cause=f"端口 {port} 已被占用",
|
|
503
|
+
fix_commands=[f"lsof -ti:{port} | xargs kill -9 2>/dev/null || true"],
|
|
504
|
+
retry_original=True,
|
|
505
|
+
confidence="medium",
|
|
506
|
+
)
|
|
507
|
+
if m:
|
|
508
|
+
port = next((g for g in m.groups() if g), "8080")
|
|
509
|
+
return FixSuggestion(
|
|
510
|
+
root_cause=f"端口 {port} 已被占用",
|
|
511
|
+
fix_commands=[f"lsof -ti:{port} | xargs kill -9 2>/dev/null || true"],
|
|
512
|
+
retry_original=True,
|
|
513
|
+
confidence="medium",
|
|
514
|
+
)
|
|
515
|
+
return None
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _fix_venv_activate(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
519
|
+
"""虚拟环境激活失败"""
|
|
520
|
+
if "source" in cmd and ("venv" in cmd or "activate" in cmd):
|
|
521
|
+
combined = stderr + stdout
|
|
522
|
+
if re.search(r'No such file|not found', combined, re.IGNORECASE):
|
|
523
|
+
return FixSuggestion(
|
|
524
|
+
root_cause="虚拟环境不存在,需要先创建",
|
|
525
|
+
fix_commands=["python3 -m venv venv"],
|
|
526
|
+
retry_original=True,
|
|
527
|
+
confidence="high",
|
|
528
|
+
)
|
|
529
|
+
return None
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _fix_git_clone_exists(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
533
|
+
"""git clone 目标目录已存在"""
|
|
534
|
+
if not re.search(r'git\s+clone', cmd, re.IGNORECASE):
|
|
535
|
+
return None
|
|
536
|
+
combined = stderr + stdout
|
|
537
|
+
m = re.search(r"destination path '([^']+)' already exists", combined)
|
|
538
|
+
if m:
|
|
539
|
+
dirname = m.group(1)
|
|
540
|
+
return FixSuggestion(
|
|
541
|
+
root_cause=f"目录 '{dirname}' 已存在,无需重新克隆",
|
|
542
|
+
fix_commands=[], # 跳过此步骤即可
|
|
543
|
+
retry_original=False,
|
|
544
|
+
confidence="high",
|
|
545
|
+
)
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _fix_gradle_error(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
550
|
+
"""Gradle/Maven 构建失败(Java 项目常见问题)"""
|
|
551
|
+
combined = stderr + stdout
|
|
552
|
+
if not re.search(r'gradlew|gradle|mvnw|mvn', cmd, re.IGNORECASE):
|
|
553
|
+
return None
|
|
554
|
+
|
|
555
|
+
# gradlew 没有执行权限或不存在
|
|
556
|
+
if re.search(r'gradlew.*No such file|gradlew.*Permission denied|mvnw.*No such file|mvnw.*Permission denied',
|
|
557
|
+
combined, re.IGNORECASE):
|
|
558
|
+
if "gradlew" in cmd:
|
|
559
|
+
return FixSuggestion(
|
|
560
|
+
root_cause="gradlew 不存在或无执行权限,回退到全局 gradle",
|
|
561
|
+
fix_commands=["gradle build -x test" if "build" in cmd else "gradle " + cmd.split("gradlew")[-1].strip()],
|
|
562
|
+
retry_original=False,
|
|
563
|
+
confidence="medium",
|
|
564
|
+
)
|
|
565
|
+
if "mvnw" in cmd:
|
|
566
|
+
return FixSuggestion(
|
|
567
|
+
root_cause="mvnw 不存在或无执行权限,回退到全局 mvn",
|
|
568
|
+
fix_commands=["mvn clean package -DskipTests"],
|
|
569
|
+
retry_original=False,
|
|
570
|
+
confidence="medium",
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
# Gradle daemon 内部错误(常见于版本不匹配)
|
|
574
|
+
if re.search(r'DaemonCommandExecution|Could not create service|Daemon.*expired', combined, re.IGNORECASE):
|
|
575
|
+
return FixSuggestion(
|
|
576
|
+
root_cause="Gradle Daemon 异常,清理缓存重试",
|
|
577
|
+
fix_commands=["gradle --stop 2>/dev/null; gradle build -x test --no-daemon"],
|
|
578
|
+
retry_original=False,
|
|
579
|
+
confidence="medium",
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Java 版本不兼容
|
|
583
|
+
if re.search(r'Unsupported class file major version|source release \d+ requires target release',
|
|
584
|
+
combined, re.IGNORECASE):
|
|
585
|
+
return FixSuggestion(
|
|
586
|
+
root_cause="Java 版本不兼容(项目需要更新的 JDK)",
|
|
587
|
+
fix_commands=["brew install openjdk@21 2>/dev/null || sudo apt-get install -y openjdk-21-jdk 2>/dev/null",
|
|
588
|
+
cmd],
|
|
589
|
+
retry_original=False,
|
|
590
|
+
confidence="medium",
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# BUILD FAILED 但非编译错误(通常是缺乏子模块或目录问题)
|
|
594
|
+
if re.search(r'FAILURE:.*Build failed|BUILD FAILED', combined, re.IGNORECASE):
|
|
595
|
+
# 成功标志在 stdout 中(部分成功的多模块项目)
|
|
596
|
+
if re.search(r'BUILD SUCCESSFUL', stdout, re.IGNORECASE):
|
|
597
|
+
return FixSuggestion(
|
|
598
|
+
root_cause="部分模块构建成功,整体标记为失败(可接受)",
|
|
599
|
+
fix_commands=[],
|
|
600
|
+
retry_original=False,
|
|
601
|
+
confidence="low",
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
return None
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _fix_haskell_toolchain(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
608
|
+
"""Haskell 构建时 ghc/cabal/stack 环境不一致"""
|
|
609
|
+
if not re.search(r'\bcabal\b|\bstack\b', cmd, re.IGNORECASE):
|
|
610
|
+
return None
|
|
611
|
+
combined = stderr + stdout
|
|
612
|
+
if re.search(r"The program 'ghc'.*could not be found|ghc.*could not be found|No compiler found", combined, re.IGNORECASE):
|
|
613
|
+
if re.search(r'\bcabal\b', cmd, re.IGNORECASE):
|
|
614
|
+
return FixSuggestion(
|
|
615
|
+
root_cause="Haskell 工具链未在同一执行环境内,cabal 看不到 ghc",
|
|
616
|
+
fix_commands=[
|
|
617
|
+
"ghcup install ghc recommended",
|
|
618
|
+
"ghcup install cabal recommended",
|
|
619
|
+
"ghcup run --ghc recommended --cabal recommended -- cabal build all",
|
|
620
|
+
],
|
|
621
|
+
retry_original=False,
|
|
622
|
+
confidence="high",
|
|
623
|
+
)
|
|
624
|
+
return FixSuggestion(
|
|
625
|
+
root_cause="Haskell 编译器未准备好,先由 ghcup 安装并再试一次",
|
|
626
|
+
fix_commands=[
|
|
627
|
+
"ghcup install ghc recommended",
|
|
628
|
+
"ghcup install stack latest",
|
|
629
|
+
],
|
|
630
|
+
retry_original=True,
|
|
631
|
+
confidence="medium",
|
|
632
|
+
)
|
|
633
|
+
if re.search(r'resolver|snapshot|wanted compiler|requires.*ghc', combined, re.IGNORECASE):
|
|
634
|
+
return FixSuggestion(
|
|
635
|
+
root_cause="Stack resolver 与当前 Haskell 工具链不兼容,优先切到 ghcup 受控环境或 cabal 路径",
|
|
636
|
+
fix_commands=[
|
|
637
|
+
"ghcup install ghc recommended",
|
|
638
|
+
"ghcup install cabal recommended",
|
|
639
|
+
"ghcup install stack latest",
|
|
640
|
+
"ghcup run --ghc recommended --cabal recommended -- cabal build all",
|
|
641
|
+
],
|
|
642
|
+
retry_original=False,
|
|
643
|
+
confidence="medium",
|
|
644
|
+
)
|
|
645
|
+
return None
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _fix_haskell_stack_extra_deps(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
649
|
+
"""Stack 旧项目在新 resolver 下缺少 extra-deps,优先注入已验证依赖集"""
|
|
650
|
+
if not re.search(r'\bstack\b', cmd, re.IGNORECASE):
|
|
651
|
+
return None
|
|
652
|
+
|
|
653
|
+
combined = stderr + stdout
|
|
654
|
+
markers = [
|
|
655
|
+
r'yi-frontend-vty',
|
|
656
|
+
r'vty-crossplatform',
|
|
657
|
+
r'vty-unix',
|
|
658
|
+
r'needed.*but not found in snapshot',
|
|
659
|
+
r'build plan',
|
|
660
|
+
]
|
|
661
|
+
if not all(re.search(marker, combined, re.IGNORECASE) for marker in markers[:3]):
|
|
662
|
+
return None
|
|
663
|
+
if not re.search(markers[3], combined, re.IGNORECASE) and not re.search(markers[4], combined, re.IGNORECASE):
|
|
664
|
+
return None
|
|
665
|
+
|
|
666
|
+
patch_stack_yaml = (
|
|
667
|
+
"perl -0pi -e 's/extra-deps:\\n - Hclip-3\\.0\\.0\\.4\\n/"
|
|
668
|
+
"extra-deps:\\n - Hclip-3.0.0.4\\n"
|
|
669
|
+
" - vty-6.5\\@sha256:43a4137de7e55cf438a8334cc525fb0e0b4efe78d2ed8bd31b0716eb34993059,3425\\n"
|
|
670
|
+
" - vty-crossplatform-0.5.0.0\\@sha256:6d057fd8a5582eac3be28c91e99ed3730b729078e107ad19107af46bbb2ea65d,3146\\n"
|
|
671
|
+
" - vty-unix-0.2.0.0\\@sha256:2af3d0bdae3c4b7b7e567ee374efe32c7439fabdf9096465ce011a6c6736e9ae,2932\\n/' stack.yaml"
|
|
672
|
+
)
|
|
673
|
+
rebuild_cmd = "ghcup run --stack latest -- stack build yi --flag yi:-pango"
|
|
674
|
+
return FixSuggestion(
|
|
675
|
+
root_cause="Stack 旧项目缺少 yi/vty 相关 extra-deps,需补齐固定版本并关闭 pango 前端",
|
|
676
|
+
fix_commands=[patch_stack_yaml, rebuild_cmd],
|
|
677
|
+
retry_original=False,
|
|
678
|
+
confidence="high",
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _fix_haskell_legacy_gui_headless(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
683
|
+
"""旧 GUI 目标拖垮整包时,尝试剥离 GUI stanza 并转向 headless/CLI 构建"""
|
|
684
|
+
if not re.search(r'\bcabal\b|\bstack\b', cmd, re.IGNORECASE):
|
|
685
|
+
return None
|
|
686
|
+
|
|
687
|
+
combined = stderr + stdout
|
|
688
|
+
old_ghc_marker = re.search(
|
|
689
|
+
r'No setup information found for ghc-\d+\.\d+\.\d+.*(?:macosx-aarch64|your platform)',
|
|
690
|
+
combined,
|
|
691
|
+
re.IGNORECASE,
|
|
692
|
+
)
|
|
693
|
+
gui_marker = re.search(
|
|
694
|
+
r'haskell-gi|gi-gtk|gi-gdk|gi-gio|gi-glib|gi-gobject|gi-pango|gi-cairo|gi-gst|gobject-introspection-1\.0',
|
|
695
|
+
combined,
|
|
696
|
+
re.IGNORECASE,
|
|
697
|
+
)
|
|
698
|
+
exact_pin_marker = re.search(
|
|
699
|
+
r'Could not resolve dependencies.*conflict:.*=>\s*base>=\d+\.\d+\s*&&\s*<\d+\.\d+|excluded by constraint .*base|\bbase == \d+\.\d+',
|
|
700
|
+
combined,
|
|
701
|
+
re.IGNORECASE | re.DOTALL,
|
|
702
|
+
)
|
|
703
|
+
legacy_resolution_marker = re.search(
|
|
704
|
+
r'Could not resolve dependencies|not found in the pkg-config database|explicit-setup-deps',
|
|
705
|
+
combined,
|
|
706
|
+
re.IGNORECASE,
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
if not old_ghc_marker and not ((gui_marker or exact_pin_marker) and legacy_resolution_marker):
|
|
710
|
+
return None
|
|
711
|
+
|
|
712
|
+
guard_cmd = (
|
|
713
|
+
"cabal_file=\"$(find . -maxdepth 1 -name '*.cabal' | head -n 1)\""
|
|
714
|
+
" && [ -n \"$cabal_file\" ]"
|
|
715
|
+
" && grep -Eq '^\\s*(library|executable)\\b' \"$cabal_file\""
|
|
716
|
+
" && grep -Eq 'haskell-gi|gi-gtk|gi-gdk|gi-gio|gi-glib|gi-gobject|gi-pango|gi-cairo|gi-gst|gobject-introspection|gtk' \"$cabal_file\""
|
|
717
|
+
)
|
|
718
|
+
patch_and_build_cmd = (
|
|
719
|
+
"cabal_file=\"$(find . -maxdepth 1 -name '*.cabal' | head -n 1)\""
|
|
720
|
+
" && cp \"$cabal_file\" \"$cabal_file.gitinstall.bak\""
|
|
721
|
+
" && perl -0pi -e 'my @chunks = split(/(?=^(?:library|executable|test-suite|benchmark|foreign-library|common|flag|source-repository)\\b)/ms, $_);"
|
|
722
|
+
" $_ = q{};"
|
|
723
|
+
" for my $chunk (@chunks) {"
|
|
724
|
+
" if ($chunk =~ /^executable\\b/ms && $chunk =~ /\\b(?:haskell-gi(?:-base)?|gi-gobject|gi-gio|gi-glib|gi-pango|gi-gdk(?:pixbuf)?|gi-gtk|gi-cairo|gi-gst(?:base|video)?|gobject-introspection|webkit)\\b/ms) { next; }"
|
|
725
|
+
" $_ .= $chunk;"
|
|
726
|
+
" }' \"$cabal_file\""
|
|
727
|
+
" && perl -0pi -e 's/==\\s*([0-9]+(?:\\.[0-9]+)*)(?:\\.\\*)?/>= $1/g' \"$cabal_file\""
|
|
728
|
+
" && printf '%s\\n' 'packages: .' 'allow-newer: true' > cabal.project.gitinstall-headless"
|
|
729
|
+
" && ghcup run --ghc recommended --cabal recommended -- cabal build all --project-file=cabal.project.gitinstall-headless"
|
|
730
|
+
)
|
|
731
|
+
return FixSuggestion(
|
|
732
|
+
root_cause="旧 GUI 目标依赖过时的 GHC/GTK 绑定,先剥离 GUI stanza 并转向 headless/CLI 构建更可靠",
|
|
733
|
+
fix_commands=[guard_cmd, patch_and_build_cmd],
|
|
734
|
+
retry_original=False,
|
|
735
|
+
confidence="medium",
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _fix_zig_darwin_sdk(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
740
|
+
"""Zig 在 macOS 上找不到 Darwin SDK"""
|
|
741
|
+
if not re.search(r'\bzig\b', cmd, re.IGNORECASE):
|
|
742
|
+
return None
|
|
743
|
+
combined = stderr + stdout
|
|
744
|
+
if re.search(r'DarwinSdkNotFound|unable to find libSystem|SDK.*macosx.*not found|xcrun: error', combined, re.IGNORECASE):
|
|
745
|
+
return FixSuggestion(
|
|
746
|
+
root_cause="macOS Apple SDK 未就绪,Zig 无法定位 Darwin SDK;继续重试价值很低,应先修复 Xcode/Command Line Tools",
|
|
747
|
+
fix_commands=[],
|
|
748
|
+
retry_original=False,
|
|
749
|
+
confidence="high",
|
|
750
|
+
outcome="trusted_failure",
|
|
751
|
+
)
|
|
752
|
+
return None
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def _fix_zig_legacy_build_api(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
756
|
+
"""Zig 老版本 build API 与当前 Zig 版本不兼容"""
|
|
757
|
+
if not re.search(r'\bzig\b', cmd, re.IGNORECASE):
|
|
758
|
+
return None
|
|
759
|
+
combined = stderr + stdout
|
|
760
|
+
if re.search(r"no field named 'root_source_file'|no field named 'source_file'|Build\.ExecutableOptions", combined, re.IGNORECASE):
|
|
761
|
+
return FixSuggestion(
|
|
762
|
+
root_cause="项目使用旧版 Zig build API,当前 Zig 0.15+ 不兼容;这类项目应优先归类为需要旧 Zig 版本的可信失败,而不是继续重试当前工具链",
|
|
763
|
+
fix_commands=[],
|
|
764
|
+
retry_original=False,
|
|
765
|
+
confidence="high",
|
|
766
|
+
outcome="trusted_failure",
|
|
767
|
+
)
|
|
768
|
+
return None
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _fix_haskell_system_libraries(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
772
|
+
"""Haskell 构建过程中缺少系统级开发库"""
|
|
773
|
+
if not re.search(r'\bcabal\b|\bstack\b|\bghc\b', cmd, re.IGNORECASE):
|
|
774
|
+
return None
|
|
775
|
+
combined = stderr + stdout
|
|
776
|
+
packages: list[str] = []
|
|
777
|
+
root_causes: list[str] = []
|
|
778
|
+
|
|
779
|
+
if re.search(r'pkg-config.*not found', combined, re.IGNORECASE):
|
|
780
|
+
packages.append(_install_pkg_cmd("pkg-config", "pkg-config", "pkg-config"))
|
|
781
|
+
root_causes.append("pkg-config")
|
|
782
|
+
if re.search(r"pcre\.h.*not found|cannot find -lpcre|libpcre", combined, re.IGNORECASE):
|
|
783
|
+
packages.append(_install_pkg_cmd("pcre", "pcre", "libpcre3-dev"))
|
|
784
|
+
root_causes.append("PCRE")
|
|
785
|
+
if re.search(r"openssl.*not found|ssl\.h.*not found|cannot find -lssl|cannot find -lcrypto", combined, re.IGNORECASE):
|
|
786
|
+
packages.append(_install_pkg_cmd("openssl", "openssl", "libssl-dev"))
|
|
787
|
+
root_causes.append("OpenSSL")
|
|
788
|
+
if re.search(r"gtk\+[-]?[23]\.0.*not found|gtk\+-2\.0-any.*not found|pkg-config package .*gtk", combined, re.IGNORECASE):
|
|
789
|
+
packages.append(_install_pkg_cmd("gtk+3", "gtk+3", "libgtk-3-dev"))
|
|
790
|
+
root_causes.append("GTK")
|
|
791
|
+
|
|
792
|
+
commands = [cmd for cmd in packages if cmd]
|
|
793
|
+
if commands:
|
|
794
|
+
if _is_macos():
|
|
795
|
+
formulas = []
|
|
796
|
+
if "PCRE" in root_causes:
|
|
797
|
+
formulas.append("pcre")
|
|
798
|
+
if "OpenSSL" in root_causes:
|
|
799
|
+
formulas.append("openssl")
|
|
800
|
+
if "GTK" in root_causes:
|
|
801
|
+
formulas.append("gtk+3")
|
|
802
|
+
env_exports = _haskell_macos_env_exports(formulas)
|
|
803
|
+
return FixSuggestion(
|
|
804
|
+
root_cause=f"Haskell 编译缺少系统库依赖或 macOS 头文件路径未导出:{', '.join(root_causes)}",
|
|
805
|
+
fix_commands=commands + [f"{env_exports}; {cmd}"],
|
|
806
|
+
retry_original=False,
|
|
807
|
+
confidence="high",
|
|
808
|
+
)
|
|
809
|
+
return FixSuggestion(
|
|
810
|
+
root_cause=f"Haskell 编译缺少系统库依赖:{', '.join(root_causes)}",
|
|
811
|
+
fix_commands=commands,
|
|
812
|
+
retry_original=True,
|
|
813
|
+
confidence="high",
|
|
814
|
+
)
|
|
815
|
+
return None
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def _fix_npm_no_package_json(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
819
|
+
"""npm install 但当前目录没有 package.json"""
|
|
820
|
+
if not re.search(r'npm\s+install', cmd, re.IGNORECASE):
|
|
821
|
+
return None
|
|
822
|
+
combined = stderr + stdout
|
|
823
|
+
if re.search(r'ENOENT.*package\.json|no such file.*package\.json', combined, re.IGNORECASE):
|
|
824
|
+
return FixSuggestion(
|
|
825
|
+
root_cause="当前目录没有 package.json(可能是多模块项目或非 Node.js 项目)",
|
|
826
|
+
fix_commands=[],
|
|
827
|
+
retry_original=False,
|
|
828
|
+
confidence="high",
|
|
829
|
+
outcome="trusted_failure",
|
|
830
|
+
)
|
|
831
|
+
return None
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _fix_network_timeout(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
835
|
+
"""网络超时 / DNS / SSL 错误"""
|
|
836
|
+
combined = stderr + stdout
|
|
837
|
+
if re.search(r'timed? ?out|ConnectionReset|ConnectionRefused|ETIMEOUT|DNS.*fail|SSL.*error|SSLCertVerification|Could not resolve host',
|
|
838
|
+
combined, re.IGNORECASE):
|
|
839
|
+
# 对 pip 加镜像源
|
|
840
|
+
if re.search(r'pip3?\s+install', cmd, re.IGNORECASE):
|
|
841
|
+
new_cmd = cmd.rstrip() + " -i https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
842
|
+
return FixSuggestion(
|
|
843
|
+
root_cause="网络连接失败,切换镜像源重试",
|
|
844
|
+
fix_commands=[new_cmd],
|
|
845
|
+
retry_original=False,
|
|
846
|
+
confidence="medium",
|
|
847
|
+
)
|
|
848
|
+
# 通用:直接重试
|
|
849
|
+
return FixSuggestion(
|
|
850
|
+
root_cause="网络连接超时,重试中",
|
|
851
|
+
fix_commands=[],
|
|
852
|
+
retry_original=True,
|
|
853
|
+
confidence="low",
|
|
854
|
+
)
|
|
855
|
+
return None
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _fix_disk_space(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
859
|
+
"""磁盘空间不足"""
|
|
860
|
+
combined = stderr + stdout
|
|
861
|
+
if re.search(r'No space left on device|ENOSPC|disk full', combined, re.IGNORECASE):
|
|
862
|
+
return FixSuggestion(
|
|
863
|
+
root_cause="磁盘空间不足,请清理后重试",
|
|
864
|
+
fix_commands=[],
|
|
865
|
+
retry_original=False,
|
|
866
|
+
confidence="high",
|
|
867
|
+
)
|
|
868
|
+
return None
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _fix_submodule_error(cmd: str, stderr: str, stdout: str) -> FixSuggestion | None:
|
|
872
|
+
"""git submodule 初始化失败"""
|
|
873
|
+
combined = stderr + stdout
|
|
874
|
+
if re.search(
|
|
875
|
+
r'submodule.*(?:fatal|error|failed)'
|
|
876
|
+
r'|(?:fatal|error).*submodule'
|
|
877
|
+
r'|Cloning into.*fatal'
|
|
878
|
+
r'|Failed to clone.*submodule'
|
|
879
|
+
r'|Unable to checkout.*submodule',
|
|
880
|
+
combined, re.IGNORECASE | re.DOTALL
|
|
881
|
+
):
|
|
882
|
+
return FixSuggestion(
|
|
883
|
+
root_cause="Git submodule 拉取失败",
|
|
884
|
+
fix_commands=["git submodule deinit --all -f", "git submodule update --init --recursive"],
|
|
885
|
+
retry_original=False,
|
|
886
|
+
confidence="medium",
|
|
887
|
+
)
|
|
888
|
+
return None
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
# ─── 规则链注册 ──────────────────────────────
|
|
892
|
+
|
|
893
|
+
# 按优先级排序(高频/简单的规则优先)
|
|
894
|
+
# 排序逻辑:
|
|
895
|
+
# 1. 可跳过/假阳性(不需要修复,直接标记成功)
|
|
896
|
+
# 2. 高频通用故障(command not found、权限、网络)
|
|
897
|
+
# 3. 语言/构建系统特定错误
|
|
898
|
+
# 4. 兜底规则
|
|
899
|
+
ERROR_FIX_RULES = [
|
|
900
|
+
_fix_git_clone_exists, # 目录已存在(可跳过)
|
|
901
|
+
_fix_npm_audit, # npm audit 警告(假阳性)
|
|
902
|
+
_fix_npm_no_package_json, # npm 找不到 package.json
|
|
903
|
+
_fix_command_not_found, # 通用命令缺失(高频!提前)
|
|
904
|
+
_fix_venv_activate, # venv 激活失败
|
|
905
|
+
_fix_pip_permission, # pip 权限
|
|
906
|
+
_fix_pip_not_found_package, # pip 包名错误
|
|
907
|
+
_fix_npm_workspace_protocol,# npm workspace: 协议不支持
|
|
908
|
+
_fix_npm_eexist, # npm 缓存冲突
|
|
909
|
+
_fix_npm_permission, # npm 权限
|
|
910
|
+
_fix_python_version, # Python 版本
|
|
911
|
+
_fix_node_version, # Node 版本
|
|
912
|
+
_fix_build_essentials, # 编译工具链
|
|
913
|
+
_fix_cmake_not_found, # CMake
|
|
914
|
+
_fix_gradle_error, # Gradle/Maven 构建失败
|
|
915
|
+
_fix_cargo_git_install_layout, # cargo install --git 的 workspace/库仓库问题
|
|
916
|
+
_fix_go_no_root_module, # go build 在无根 go.mod 仓库中失败
|
|
917
|
+
_fix_pip_build_wheel, # pip install C 扩展构建失败
|
|
918
|
+
_fix_rust_compile_error, # Rust 编译依赖
|
|
919
|
+
_fix_haskell_legacy_gui_headless, # 旧 GUI 目标拖垮整包时转向 headless/CLI 构建
|
|
920
|
+
_fix_haskell_stack_extra_deps, # yi/vty 类 Stack 旧项目缺少 extra-deps
|
|
921
|
+
_fix_haskell_toolchain, # Haskell ghc/cabal/stack 工具链错位
|
|
922
|
+
_fix_haskell_system_libraries, # Haskell 缺少 pkg-config/PCRE/OpenSSL 等系统库
|
|
923
|
+
_fix_zig_darwin_sdk, # Zig 在 macOS 上找不到 Darwin SDK
|
|
924
|
+
_fix_zig_legacy_build_api, # Zig 旧 build API 与当前版本不兼容
|
|
925
|
+
_fix_network_timeout, # 网络超时
|
|
926
|
+
_fix_port_in_use, # 端口占用
|
|
927
|
+
_fix_submodule_error, # git submodule
|
|
928
|
+
_fix_disk_space, # 磁盘空间
|
|
929
|
+
]
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def diagnose(command: str, stderr: str, stdout: str = "") -> FixSuggestion | None:
|
|
933
|
+
"""
|
|
934
|
+
主入口:给一个失败的命令 + 其 stderr/stdout,返回修复建议。
|
|
935
|
+
|
|
936
|
+
遍历所有规则,返回第一个匹配的 FixSuggestion,
|
|
937
|
+
如果全部不匹配返回 None(需要 LLM 介入)。
|
|
938
|
+
|
|
939
|
+
示例:
|
|
940
|
+
fix = diagnose("pip install torch", "externally-managed-environment")
|
|
941
|
+
if fix:
|
|
942
|
+
print(fix.root_cause) # "系统 Python 受保护..."
|
|
943
|
+
print(fix.fix_commands) # ["python3 -m venv venv", ...]
|
|
944
|
+
print(fix.retry_original) # True
|
|
945
|
+
"""
|
|
946
|
+
for rule_fn in ERROR_FIX_RULES:
|
|
947
|
+
try:
|
|
948
|
+
result = rule_fn(command, stderr, stdout)
|
|
949
|
+
if result is not None:
|
|
950
|
+
return result
|
|
951
|
+
except Exception:
|
|
952
|
+
continue
|
|
953
|
+
return None
|