jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.6__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +243 -139
- jarvis/jarvis_agent/agent_manager.py +5 -10
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +265 -15
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +113 -98
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +6 -12
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +77 -14
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +12 -21
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/task_analyzer.py +26 -4
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/web_server.py +55 -20
- jarvis/jarvis_c2rust/__init__.py +5 -5
- jarvis/jarvis_c2rust/cli.py +461 -499
- jarvis/jarvis_c2rust/collector.py +45 -53
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +264 -132
- jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +1592 -395
- jarvis/jarvis_c2rust/transpiler.py +1722 -1064
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +2 -3
- jarvis/jarvis_code_agent/code_agent.py +394 -320
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
- jarvis/jarvis_code_agent/lint.py +258 -27
- jarvis/jarvis_code_agent/utils.py +0 -1
- jarvis/jarvis_code_analysis/code_review.py +19 -24
- jarvis/jarvis_data/config_schema.json +53 -26
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +44 -49
- jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
- jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +79 -61
- jarvis/jarvis_multi_agent/main.py +3 -7
- jarvis/jarvis_platform/base.py +469 -199
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +65 -27
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +31 -42
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +49 -51
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +220 -3520
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/cli.py +29 -6
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +83 -4
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +7 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +1 -1
- jarvis/jarvis_stats/stats.py +7 -7
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +20 -23
- jarvis/jarvis_tools/edit_file.py +1066 -0
- jarvis/jarvis_tools/execute_script.py +42 -21
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +11 -20
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1525 -87
- jarvis/jarvis_tools/read_symbols.py +2 -3
- jarvis/jarvis_tools/read_webpage.py +7 -10
- jarvis/jarvis_tools/registry.py +370 -181
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +105 -0
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +3 -7
- jarvis/jarvis_tools/sub_agent.py +17 -6
- jarvis/jarvis_tools/sub_code_agent.py +14 -16
- jarvis/jarvis_tools/virtual_tty.py +54 -32
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +98 -63
- jarvis/jarvis_utils/embedding.py +5 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +81 -67
- jarvis/jarvis_utils/input.py +24 -49
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +33 -35
- jarvis/jarvis_utils/utils.py +245 -202
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/METADATA +205 -70
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- jarvis/jarvis_agent/edit_file_handler.py +0 -584
- jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
- jarvis/jarvis_agent/task_planner.py +0 -496
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ C2Rust 转译器模块
|
|
|
6
6
|
- 基于 scanner 生成的 translation_order.jsonl 顺序,逐个函数进行转译
|
|
7
7
|
- 为每个函数:
|
|
8
8
|
1) 准备上下文:C 源码片段+位置信息、被调用符号(若已转译则提供Rust模块与符号,否则提供原C位置信息)、crate目录结构
|
|
9
|
-
2)
|
|
9
|
+
2) 创建"模块选择与签名Agent":让其选择合适的Rust模块路径,并在summary输出函数签名
|
|
10
10
|
3) 记录当前进度到 progress.json
|
|
11
11
|
4) 基于上述信息与落盘位置,创建 CodeAgent 生成转译后的Rust函数
|
|
12
12
|
5) 尝试 cargo build,如失败则携带错误上下文创建 CodeAgent 修复,直到构建通过或达到上限
|
|
@@ -24,432 +24,43 @@ import os
|
|
|
24
24
|
import re
|
|
25
25
|
import subprocess
|
|
26
26
|
import time
|
|
27
|
-
from dataclasses import dataclass
|
|
28
27
|
from pathlib import Path
|
|
29
28
|
from typing import Any, Dict, List, Optional, Tuple, Union, Set
|
|
30
29
|
|
|
31
30
|
import typer
|
|
32
31
|
|
|
33
|
-
from jarvis.jarvis_c2rust.scanner import compute_translation_order_jsonl
|
|
34
32
|
from jarvis.jarvis_agent import Agent
|
|
33
|
+
from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL
|
|
35
34
|
from jarvis.jarvis_code_agent.code_agent import CodeAgent
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end_col: int
|
|
66
|
-
refs: List[str]
|
|
67
|
-
# 额外元信息(来自 symbols/items):函数签名、返回类型与参数(可选)
|
|
68
|
-
signature: str = ""
|
|
69
|
-
return_type: str = ""
|
|
70
|
-
params: Optional[List[Dict[str, str]]] = None
|
|
71
|
-
# 来自库替代阶段的上下文元数据(若存在)
|
|
72
|
-
lib_replacement: Optional[Dict[str, Any]] = None
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class _DbLoader:
|
|
76
|
-
"""读取 symbols.jsonl 并提供按 id/name 查询与源码片段读取"""
|
|
77
|
-
|
|
78
|
-
def __init__(self, project_root: Union[str, Path]) -> None:
|
|
79
|
-
self.project_root = Path(project_root).resolve()
|
|
80
|
-
self.data_dir = self.project_root / C2RUST_DIRNAME
|
|
81
|
-
|
|
82
|
-
self.symbols_path = self.data_dir / SYMBOLS_JSONL
|
|
83
|
-
# 统一流程:仅使用 symbols.jsonl,不再兼容 functions.jsonl
|
|
84
|
-
if not self.symbols_path.exists():
|
|
85
|
-
raise FileNotFoundError(
|
|
86
|
-
f"在目录下未找到 symbols.jsonl: {self.data_dir}"
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
self.fn_by_id: Dict[int, FnRecord] = {}
|
|
90
|
-
self.name_to_id: Dict[str, int] = {}
|
|
91
|
-
self._load()
|
|
92
|
-
|
|
93
|
-
def _load(self) -> None:
|
|
94
|
-
"""
|
|
95
|
-
读取统一的 symbols.jsonl。
|
|
96
|
-
不区分函数与类型定义,均加载为通用记录(位置与引用信息)。
|
|
97
|
-
"""
|
|
98
|
-
def _iter_records_from_file(path: Path):
|
|
99
|
-
try:
|
|
100
|
-
with path.open("r", encoding="utf-8") as f:
|
|
101
|
-
idx = 0
|
|
102
|
-
for line in f:
|
|
103
|
-
line = line.strip()
|
|
104
|
-
if not line:
|
|
105
|
-
continue
|
|
106
|
-
try:
|
|
107
|
-
obj = json.loads(line)
|
|
108
|
-
except Exception:
|
|
109
|
-
continue
|
|
110
|
-
idx += 1
|
|
111
|
-
yield idx, obj
|
|
112
|
-
except FileNotFoundError:
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
# 加载所有符号记录(函数、类型等)
|
|
116
|
-
for idx, obj in _iter_records_from_file(self.symbols_path):
|
|
117
|
-
fid = int(obj.get("id") or idx)
|
|
118
|
-
nm = obj.get("name") or ""
|
|
119
|
-
qn = obj.get("qualified_name") or ""
|
|
120
|
-
fp = obj.get("file") or ""
|
|
121
|
-
refs = obj.get("ref")
|
|
122
|
-
# 统一使用列表类型的引用字段
|
|
123
|
-
if not isinstance(refs, list):
|
|
124
|
-
refs = []
|
|
125
|
-
refs = [c for c in refs if isinstance(c, str) and c]
|
|
126
|
-
sr = int(obj.get("start_line") or 0)
|
|
127
|
-
sc = int(obj.get("start_col") or 0)
|
|
128
|
-
er = int(obj.get("end_line") or 0)
|
|
129
|
-
ec = int(obj.get("end_col") or 0)
|
|
130
|
-
lr = obj.get("lib_replacement") if isinstance(obj.get("lib_replacement"), dict) else None
|
|
131
|
-
rec = FnRecord(
|
|
132
|
-
id=fid,
|
|
133
|
-
name=nm,
|
|
134
|
-
qname=qn,
|
|
135
|
-
file=fp,
|
|
136
|
-
start_line=sr,
|
|
137
|
-
start_col=sc,
|
|
138
|
-
end_line=er,
|
|
139
|
-
end_col=ec,
|
|
140
|
-
refs=refs,
|
|
141
|
-
lib_replacement=lr,
|
|
142
|
-
)
|
|
143
|
-
self.fn_by_id[fid] = rec
|
|
144
|
-
if nm:
|
|
145
|
-
self.name_to_id.setdefault(nm, fid)
|
|
146
|
-
if qn:
|
|
147
|
-
self.name_to_id.setdefault(qn, fid)
|
|
148
|
-
|
|
149
|
-
def get(self, fid: int) -> Optional[FnRecord]:
|
|
150
|
-
return self.fn_by_id.get(fid)
|
|
151
|
-
|
|
152
|
-
def get_id_by_name(self, name_or_qname: str) -> Optional[int]:
|
|
153
|
-
return self.name_to_id.get(name_or_qname)
|
|
154
|
-
|
|
155
|
-
def read_source_span(self, rec: FnRecord) -> str:
|
|
156
|
-
"""按起止行读取源码片段(忽略列边界,尽量完整)"""
|
|
157
|
-
try:
|
|
158
|
-
p = Path(rec.file)
|
|
159
|
-
# 若记录为相对路径,基于 project_root 解析
|
|
160
|
-
if not p.is_absolute():
|
|
161
|
-
p = (self.project_root / p).resolve()
|
|
162
|
-
if not p.exists():
|
|
163
|
-
return ""
|
|
164
|
-
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
165
|
-
s = max(1, rec.start_line)
|
|
166
|
-
e = min(len(lines), max(rec.end_line, s))
|
|
167
|
-
# Python 索引从0开始,包含终止行
|
|
168
|
-
chunk = "\n".join(lines[s - 1 : e])
|
|
169
|
-
return chunk
|
|
170
|
-
except Exception:
|
|
171
|
-
return ""
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
class _SymbolMapJsonl:
|
|
175
|
-
"""
|
|
176
|
-
JSONL 形式的符号映射管理:
|
|
177
|
-
- 每行一条记录,支持同名函数的多条映射(用于处理重载/同名符号)
|
|
178
|
-
- 记录字段:
|
|
179
|
-
{
|
|
180
|
-
"c_name": "<简单名>",
|
|
181
|
-
"c_qname": "<限定名,可为空字符串>",
|
|
182
|
-
"c_file": "<源文件路径>",
|
|
183
|
-
"start_line": <int>,
|
|
184
|
-
"end_line": <int>,
|
|
185
|
-
"module": "src/xxx.rs 或 src/xxx/mod.rs",
|
|
186
|
-
"rust_symbol": "<Rust函数名>",
|
|
187
|
-
"updated_at": "YYYY-MM-DDTHH:MM:SS"
|
|
188
|
-
}
|
|
189
|
-
- 提供按名称(c_name/c_qname)查询、按源位置判断是否已记录等能力
|
|
190
|
-
"""
|
|
191
|
-
|
|
192
|
-
def __init__(self, jsonl_path: Path, legacy_json_path: Optional[Path] = None) -> None:
|
|
193
|
-
self.jsonl_path = jsonl_path
|
|
194
|
-
self.legacy_json_path = legacy_json_path
|
|
195
|
-
self.records: List[Dict[str, Any]] = []
|
|
196
|
-
# 索引:名称 -> 记录列表索引
|
|
197
|
-
self.by_key: Dict[str, List[int]] = {}
|
|
198
|
-
# 唯一定位(避免同名冲突):(c_file, start_line, end_line, c_qname or c_name) -> 记录索引列表
|
|
199
|
-
self.by_pos: Dict[Tuple[str, int, int, str], List[int]] = {}
|
|
200
|
-
self._load()
|
|
201
|
-
|
|
202
|
-
def _load(self) -> None:
|
|
203
|
-
self.records = []
|
|
204
|
-
self.by_key = {}
|
|
205
|
-
self.by_pos = {}
|
|
206
|
-
# 读取 JSONL
|
|
207
|
-
if self.jsonl_path.exists():
|
|
208
|
-
try:
|
|
209
|
-
with self.jsonl_path.open("r", encoding="utf-8") as f:
|
|
210
|
-
for line in f:
|
|
211
|
-
line = line.strip()
|
|
212
|
-
if not line:
|
|
213
|
-
continue
|
|
214
|
-
try:
|
|
215
|
-
obj = json.loads(line)
|
|
216
|
-
except Exception:
|
|
217
|
-
continue
|
|
218
|
-
self._add_record_in_memory(obj)
|
|
219
|
-
except Exception:
|
|
220
|
-
pass
|
|
221
|
-
# 兼容旧版 symbol_map.json(若存在则读入为“最后一条”)
|
|
222
|
-
elif self.legacy_json_path and self.legacy_json_path.exists():
|
|
223
|
-
try:
|
|
224
|
-
legacy = json.loads(self.legacy_json_path.read_text(encoding="utf-8"))
|
|
225
|
-
if isinstance(legacy, dict):
|
|
226
|
-
for k, v in legacy.items():
|
|
227
|
-
rec = {
|
|
228
|
-
"c_name": k,
|
|
229
|
-
"c_qname": k,
|
|
230
|
-
"c_file": "",
|
|
231
|
-
"start_line": 0,
|
|
232
|
-
"end_line": 0,
|
|
233
|
-
"module": v.get("module"),
|
|
234
|
-
"rust_symbol": v.get("rust_symbol"),
|
|
235
|
-
"updated_at": v.get("updated_at"),
|
|
236
|
-
}
|
|
237
|
-
self._add_record_in_memory(rec)
|
|
238
|
-
except Exception:
|
|
239
|
-
pass
|
|
240
|
-
|
|
241
|
-
def _add_record_in_memory(self, rec: Dict[str, Any]) -> None:
|
|
242
|
-
idx = len(self.records)
|
|
243
|
-
self.records.append(rec)
|
|
244
|
-
for key in [rec.get("c_name") or "", rec.get("c_qname") or ""]:
|
|
245
|
-
k = str(key or "").strip()
|
|
246
|
-
if not k:
|
|
247
|
-
continue
|
|
248
|
-
self.by_key.setdefault(k, []).append(idx)
|
|
249
|
-
pos_key = (str(rec.get("c_file") or ""), int(rec.get("start_line") or 0), int(rec.get("end_line") or 0), str(rec.get("c_qname") or rec.get("c_name") or ""))
|
|
250
|
-
self.by_pos.setdefault(pos_key, []).append(idx)
|
|
251
|
-
|
|
252
|
-
def has_symbol(self, sym: str) -> bool:
|
|
253
|
-
return bool(self.by_key.get(sym))
|
|
254
|
-
|
|
255
|
-
def get(self, sym: str) -> List[Dict[str, Any]]:
|
|
256
|
-
idxs = self.by_key.get(sym) or []
|
|
257
|
-
return [self.records[i] for i in idxs]
|
|
258
|
-
|
|
259
|
-
def get_any(self, sym: str) -> Optional[Dict[str, Any]]:
|
|
260
|
-
recs = self.get(sym)
|
|
261
|
-
return recs[-1] if recs else None
|
|
262
|
-
|
|
263
|
-
def has_rec(self, rec: FnRecord) -> bool:
|
|
264
|
-
key = (str(rec.file or ""), int(rec.start_line or 0), int(rec.end_line or 0), str(rec.qname or rec.name or ""))
|
|
265
|
-
return bool(self.by_pos.get(key))
|
|
266
|
-
|
|
267
|
-
def add(self, rec: FnRecord, module: str, rust_symbol: str) -> None:
|
|
268
|
-
obj = {
|
|
269
|
-
"c_name": rec.name or "",
|
|
270
|
-
"c_qname": rec.qname or "",
|
|
271
|
-
"c_file": rec.file or "",
|
|
272
|
-
"start_line": int(rec.start_line or 0),
|
|
273
|
-
"end_line": int(rec.end_line or 0),
|
|
274
|
-
"module": str(module or ""),
|
|
275
|
-
"rust_symbol": str(rust_symbol or (rec.name or f"fn_{rec.id}")),
|
|
276
|
-
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime()),
|
|
277
|
-
}
|
|
278
|
-
# 先写盘,再更新内存索引
|
|
279
|
-
try:
|
|
280
|
-
self.jsonl_path.parent.mkdir(parents=True, exist_ok=True)
|
|
281
|
-
with self.jsonl_path.open("a", encoding="utf-8") as f:
|
|
282
|
-
f.write(json.dumps(obj, ensure_ascii=False) + "\n")
|
|
283
|
-
except Exception:
|
|
284
|
-
pass
|
|
285
|
-
self._add_record_in_memory(obj)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
def _ensure_order_file(project_root: Path) -> Path:
|
|
289
|
-
"""确保 translation_order.jsonl 存在且包含有效步骤;仅基于 symbols.jsonl 生成,不使用任何回退。"""
|
|
290
|
-
data_dir = project_root / C2RUST_DIRNAME
|
|
291
|
-
order_path = data_dir / ORDER_JSONL
|
|
292
|
-
typer.secho(f"[c2rust-transpiler][order] 目标顺序文件: {order_path}", fg=typer.colors.BLUE)
|
|
293
|
-
|
|
294
|
-
def _has_steps(p: Path) -> bool:
|
|
295
|
-
try:
|
|
296
|
-
steps = _iter_order_steps(p)
|
|
297
|
-
return bool(steps)
|
|
298
|
-
except Exception:
|
|
299
|
-
return False
|
|
300
|
-
|
|
301
|
-
# 已存在则校验是否有步骤
|
|
302
|
-
typer.secho(f"[c2rust-transpiler][order] 检查现有顺序文件有效性: {order_path}", fg=typer.colors.BLUE)
|
|
303
|
-
if order_path.exists():
|
|
304
|
-
if _has_steps(order_path):
|
|
305
|
-
typer.secho(f"[c2rust-transpiler][order] 现有顺序文件有效,将使用 {order_path}", fg=typer.colors.GREEN)
|
|
306
|
-
return order_path
|
|
307
|
-
# 为空或不可读:基于标准路径重新计算(仅 symbols.jsonl)
|
|
308
|
-
typer.secho("[c2rust-transpiler][order] 现有顺序文件为空/无效,正基于 symbols.jsonl 重新计算", fg=typer.colors.YELLOW)
|
|
309
|
-
try:
|
|
310
|
-
compute_translation_order_jsonl(data_dir, out_path=order_path)
|
|
311
|
-
except Exception as e:
|
|
312
|
-
raise RuntimeError(f"重新计算翻译顺序失败: {e}")
|
|
313
|
-
return order_path
|
|
314
|
-
|
|
315
|
-
# 不存在:按标准路径生成到固定文件名(仅 symbols.jsonl)
|
|
316
|
-
try:
|
|
317
|
-
compute_translation_order_jsonl(data_dir, out_path=order_path)
|
|
318
|
-
except Exception as e:
|
|
319
|
-
raise RuntimeError(f"计算翻译顺序失败: {e}")
|
|
320
|
-
|
|
321
|
-
typer.secho(f"[c2rust-transpiler][order] 已生成顺序文件: {order_path} (exists={order_path.exists()})", fg=typer.colors.BLUE)
|
|
322
|
-
if not order_path.exists():
|
|
323
|
-
raise FileNotFoundError(f"计算后未找到 translation_order.jsonl: {order_path}")
|
|
324
|
-
|
|
325
|
-
# 最终校验:若仍无有效步骤,直接报错并提示先执行 scan 或检查 symbols.jsonl
|
|
326
|
-
if not _has_steps(order_path):
|
|
327
|
-
raise RuntimeError("translation_order.jsonl 无有效步骤。请先执行 'jarvis-c2rust scan' 生成 symbols.jsonl 并重试。")
|
|
328
|
-
|
|
329
|
-
return order_path
|
|
330
|
-
|
|
331
|
-
def _iter_order_steps(order_jsonl: Path) -> List[List[int]]:
|
|
332
|
-
"""
|
|
333
|
-
读取翻译顺序(兼容新旧格式),返回按步骤的函数id序列列表。
|
|
334
|
-
新格式:每行包含 "ids": [int, ...] 以及 "items": [完整符号对象,...]。
|
|
335
|
-
不再兼容旧格式(不支持 "records"/"symbols" 键)。
|
|
336
|
-
"""
|
|
337
|
-
# 旧格式已移除:不再需要基于 symbols.jsonl 的 name->id 映射
|
|
338
|
-
|
|
339
|
-
steps: List[List[int]] = []
|
|
340
|
-
with order_jsonl.open("r", encoding="utf-8") as f:
|
|
341
|
-
for ln in f:
|
|
342
|
-
ln = ln.strip()
|
|
343
|
-
if not ln:
|
|
344
|
-
continue
|
|
345
|
-
try:
|
|
346
|
-
obj = json.loads(ln)
|
|
347
|
-
except Exception:
|
|
348
|
-
continue
|
|
349
|
-
|
|
350
|
-
ids = obj.get("ids")
|
|
351
|
-
if isinstance(ids, list) and ids:
|
|
352
|
-
# 新格式:仅支持 ids
|
|
353
|
-
try:
|
|
354
|
-
ids_int = [int(x) for x in ids if isinstance(x, (int, str)) and str(x).strip()]
|
|
355
|
-
except Exception:
|
|
356
|
-
ids_int = []
|
|
357
|
-
if ids_int:
|
|
358
|
-
steps.append(ids_int)
|
|
359
|
-
continue
|
|
360
|
-
# 不支持旧格式(无 ids 则跳过该行)
|
|
361
|
-
return steps
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def _dir_tree(root: Path) -> str:
|
|
365
|
-
"""格式化 crate 目录结构(过滤部分常见目录)"""
|
|
366
|
-
lines: List[str] = []
|
|
367
|
-
exclude = {".git", "target", ".jarvis"}
|
|
368
|
-
if not root.exists():
|
|
369
|
-
return ""
|
|
370
|
-
for p in sorted(root.rglob("*")):
|
|
371
|
-
if any(part in exclude for part in p.parts):
|
|
372
|
-
continue
|
|
373
|
-
rel = p.relative_to(root)
|
|
374
|
-
depth = len(rel.parts) - 1
|
|
375
|
-
indent = " " * depth
|
|
376
|
-
name = rel.name + ("/" if p.is_dir() else "")
|
|
377
|
-
lines.append(f"{indent}- {name}")
|
|
378
|
-
return "\n".join(lines)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def _default_crate_dir(project_root: Path) -> Path:
|
|
382
|
-
"""遵循 llm_module_agent 的默认crate目录选择:<parent>/<cwd.name>_rs(与当前目录同级)当传入为 '.' 时"""
|
|
383
|
-
try:
|
|
384
|
-
cwd = Path(".").resolve()
|
|
385
|
-
if project_root.resolve() == cwd:
|
|
386
|
-
return cwd.parent / f"{cwd.name}_rs"
|
|
387
|
-
else:
|
|
388
|
-
return project_root
|
|
389
|
-
except Exception:
|
|
390
|
-
return project_root
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
def _read_json(path: Path, default: Any) -> Any:
|
|
394
|
-
try:
|
|
395
|
-
if path.exists():
|
|
396
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
397
|
-
except Exception:
|
|
398
|
-
pass
|
|
399
|
-
return default
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def _write_json(path: Path, obj: Any) -> None:
|
|
403
|
-
"""原子性写入JSON文件:先写入临时文件,再重命名"""
|
|
404
|
-
try:
|
|
405
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
406
|
-
# 使用临时文件确保原子性
|
|
407
|
-
temp_path = path.with_suffix(path.suffix + ".tmp")
|
|
408
|
-
temp_path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
409
|
-
# 原子性重命名
|
|
410
|
-
temp_path.replace(path)
|
|
411
|
-
except Exception:
|
|
412
|
-
# 如果原子写入失败,回退到直接写入
|
|
413
|
-
try:
|
|
414
|
-
path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
415
|
-
except Exception:
|
|
416
|
-
pass
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
def _extract_json_from_summary(text: str) -> Tuple[Dict[str, Any], Optional[str]]:
|
|
420
|
-
"""
|
|
421
|
-
从 Agent summary 中提取结构化数据(仅支持 YAML):
|
|
422
|
-
- 仅在 <SUMMARY>...</SUMMARY> 块内查找;
|
|
423
|
-
- 只接受 <yaml>...</yaml> 标签包裹的 YAML 对象;
|
|
424
|
-
返回(解析结果, 错误信息)
|
|
425
|
-
如果解析成功,返回(data, None)
|
|
426
|
-
如果解析失败,返回({}, 错误信息)
|
|
427
|
-
"""
|
|
428
|
-
if not isinstance(text, str) or not text.strip():
|
|
429
|
-
return {}, "摘要文本为空"
|
|
430
|
-
|
|
431
|
-
# 提取 <SUMMARY> 块
|
|
432
|
-
m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", text, flags=re.IGNORECASE)
|
|
433
|
-
block = (m.group(1) if m else text).strip()
|
|
434
|
-
|
|
435
|
-
# 仅解析 <yaml>...</yaml>
|
|
436
|
-
mm = re.search(r"<yaml>([\s\S]*?)</yaml>", block, flags=re.IGNORECASE)
|
|
437
|
-
raw_yaml = mm.group(1).strip() if mm else None
|
|
438
|
-
if not raw_yaml:
|
|
439
|
-
return {}, "未找到 <yaml> 或 </yaml> 标签,或标签内容为空"
|
|
440
|
-
|
|
441
|
-
try:
|
|
442
|
-
import yaml # type: ignore
|
|
443
|
-
try:
|
|
444
|
-
obj = yaml.safe_load(raw_yaml)
|
|
445
|
-
except Exception as yaml_err:
|
|
446
|
-
error_msg = f"YAML 解析失败: {str(yaml_err)}"
|
|
447
|
-
return {}, error_msg
|
|
448
|
-
if isinstance(obj, dict):
|
|
449
|
-
return obj, None
|
|
450
|
-
return {}, f"YAML 解析结果不是字典,而是 {type(obj).__name__}"
|
|
451
|
-
except Exception as e:
|
|
452
|
-
return {}, f"解析过程发生异常: {str(e)}"
|
|
35
|
+
from jarvis.jarvis_utils.git_utils import get_latest_commit_hash, get_diff_between_commits
|
|
36
|
+
from jarvis.jarvis_utils.config import get_max_input_token_count
|
|
37
|
+
|
|
38
|
+
from jarvis.jarvis_c2rust.constants import (
|
|
39
|
+
C2RUST_DIRNAME,
|
|
40
|
+
CONFIG_JSON,
|
|
41
|
+
CONSECUTIVE_FIX_FAILURE_THRESHOLD,
|
|
42
|
+
DEFAULT_CHECK_MAX_RETRIES,
|
|
43
|
+
DEFAULT_PLAN_MAX_RETRIES,
|
|
44
|
+
DEFAULT_PLAN_MAX_RETRIES_ENTRY,
|
|
45
|
+
DEFAULT_REVIEW_MAX_ITERATIONS,
|
|
46
|
+
DEFAULT_TEST_MAX_RETRIES,
|
|
47
|
+
ERROR_SUMMARY_MAX_LENGTH,
|
|
48
|
+
MAX_FUNCTION_RETRIES,
|
|
49
|
+
PROGRESS_JSON,
|
|
50
|
+
SYMBOL_MAP_JSONL,
|
|
51
|
+
)
|
|
52
|
+
from jarvis.jarvis_c2rust.loaders import _SymbolMapJsonl
|
|
53
|
+
from jarvis.jarvis_c2rust.models import FnRecord
|
|
54
|
+
from jarvis.jarvis_c2rust.utils import (
|
|
55
|
+
check_and_handle_test_deletion,
|
|
56
|
+
default_crate_dir,
|
|
57
|
+
dir_tree,
|
|
58
|
+
ensure_order_file,
|
|
59
|
+
extract_json_from_summary,
|
|
60
|
+
iter_order_steps,
|
|
61
|
+
read_json,
|
|
62
|
+
write_json,
|
|
63
|
+
)
|
|
453
64
|
|
|
454
65
|
|
|
455
66
|
class Transpiler:
|
|
@@ -463,17 +74,16 @@ class Transpiler:
|
|
|
463
74
|
check_max_retries: Optional[int] = None, # cargo check 阶段最大重试次数(0表示无限重试)
|
|
464
75
|
test_max_retries: Optional[int] = None, # cargo test 阶段最大重试次数(0表示无限重试)
|
|
465
76
|
review_max_iterations: int = DEFAULT_REVIEW_MAX_ITERATIONS, # 审查阶段最大迭代次数(0表示无限重试)
|
|
466
|
-
|
|
467
|
-
|
|
77
|
+
disabled_libraries: Optional[List[str]] = None, # 禁用库列表(在实现时禁止使用这些库)
|
|
78
|
+
root_symbols: Optional[List[str]] = None, # 根符号列表(这些符号对应的接口实现时要求对外暴露,main除外)
|
|
468
79
|
non_interactive: bool = True,
|
|
469
80
|
) -> None:
|
|
470
81
|
self.project_root = Path(project_root).resolve()
|
|
471
82
|
self.data_dir = self.project_root / C2RUST_DIRNAME
|
|
472
83
|
self.progress_path = self.data_dir / PROGRESS_JSON
|
|
84
|
+
self.config_path = self.data_dir / CONFIG_JSON
|
|
473
85
|
# JSONL 路径
|
|
474
86
|
self.symbol_map_path = self.data_dir / SYMBOL_MAP_JSONL
|
|
475
|
-
# 兼容旧版 JSON 字典格式
|
|
476
|
-
self.legacy_symbol_map_path = self.data_dir / LEGACY_SYMBOL_MAP_JSON
|
|
477
87
|
self.llm_group = llm_group
|
|
478
88
|
self.plan_max_retries = plan_max_retries
|
|
479
89
|
# 兼容旧接口:如果只设置了 max_retries,则同时用于 check 和 test
|
|
@@ -485,19 +95,51 @@ class Transpiler:
|
|
|
485
95
|
self.test_max_retries = test_max_retries if test_max_retries is not None else DEFAULT_TEST_MAX_RETRIES
|
|
486
96
|
self.max_retries = max(self.check_max_retries, self.test_max_retries) # 保持兼容性
|
|
487
97
|
self.review_max_iterations = review_max_iterations
|
|
488
|
-
self.resume = resume
|
|
489
|
-
self.only = set(only or [])
|
|
490
98
|
self.non_interactive = non_interactive
|
|
491
|
-
typer.secho(f"[c2rust-transpiler][init] 初始化参数: project_root={self.project_root} crate_dir={Path(crate_dir) if crate_dir else _default_crate_dir(self.project_root)} llm_group={self.llm_group} plan_max_retries={self.plan_max_retries} check_max_retries={self.check_max_retries} test_max_retries={self.test_max_retries} review_max_iterations={self.review_max_iterations} resume={self.resume} only={sorted(list(self.only)) if self.only else []} non_interactive={self.non_interactive}", fg=typer.colors.BLUE)
|
|
492
99
|
|
|
493
|
-
self.crate_dir = Path(crate_dir) if crate_dir else
|
|
100
|
+
self.crate_dir = Path(crate_dir) if crate_dir else default_crate_dir(self.project_root)
|
|
494
101
|
# 使用自包含的 order.jsonl 记录构建索引,避免依赖 symbols.jsonl
|
|
495
102
|
self.fn_index_by_id: Dict[int, FnRecord] = {}
|
|
496
103
|
self.fn_name_to_id: Dict[str, int] = {}
|
|
497
104
|
|
|
498
|
-
|
|
105
|
+
# 断点续跑功能默认始终启用
|
|
106
|
+
self.resume = True
|
|
107
|
+
|
|
108
|
+
# 读取进度文件(仅用于进度信息,不包含配置)
|
|
109
|
+
default_progress = {"current": None, "converted": []}
|
|
110
|
+
self.progress: Dict[str, Any] = read_json(self.progress_path, default_progress)
|
|
111
|
+
|
|
112
|
+
# 从独立的配置文件加载配置(支持从 progress.json 向后兼容迁移)
|
|
113
|
+
config = self._load_config()
|
|
114
|
+
|
|
115
|
+
# 如果提供了新的根符号或禁用库,更新配置;否则从配置文件中恢复
|
|
116
|
+
# 优先使用传入的参数,如果为 None 则从配置文件恢复
|
|
117
|
+
if root_symbols is not None:
|
|
118
|
+
# 传入的参数不为 None,使用传入的值并保存
|
|
119
|
+
self.root_symbols = root_symbols
|
|
120
|
+
else:
|
|
121
|
+
# 传入的参数为 None,从配置文件恢复
|
|
122
|
+
# 如果配置文件中有配置则使用,否则使用空列表
|
|
123
|
+
self.root_symbols = config.get("root_symbols", [])
|
|
124
|
+
|
|
125
|
+
if disabled_libraries is not None:
|
|
126
|
+
# 传入的参数不为 None,使用传入的值并保存
|
|
127
|
+
self.disabled_libraries = disabled_libraries
|
|
128
|
+
else:
|
|
129
|
+
# 传入的参数为 None,从配置文件恢复
|
|
130
|
+
# 如果配置文件中有配置则使用,否则使用空列表
|
|
131
|
+
self.disabled_libraries = config.get("disabled_libraries", [])
|
|
132
|
+
|
|
133
|
+
# 从配置文件读取附加说明(不支持通过参数传入,只能通过配置文件设置)
|
|
134
|
+
self.additional_notes = config.get("additional_notes", "")
|
|
135
|
+
|
|
136
|
+
# 保存配置到独立的配置文件
|
|
137
|
+
self._save_config()
|
|
138
|
+
|
|
139
|
+
# 在初始化完成后打印日志
|
|
140
|
+
typer.secho(f"[c2rust-transpiler][init] 初始化参数: project_root={self.project_root} crate_dir={self.crate_dir} llm_group={self.llm_group} plan_max_retries={self.plan_max_retries} check_max_retries={self.check_max_retries} test_max_retries={self.test_max_retries} review_max_iterations={self.review_max_iterations} disabled_libraries={self.disabled_libraries} root_symbols={self.root_symbols} non_interactive={self.non_interactive}", fg=typer.colors.BLUE)
|
|
499
141
|
# 使用 JSONL 存储的符号映射
|
|
500
|
-
self.symbol_map = _SymbolMapJsonl(self.symbol_map_path
|
|
142
|
+
self.symbol_map = _SymbolMapJsonl(self.symbol_map_path)
|
|
501
143
|
|
|
502
144
|
# 当前函数上下文与Agent复用缓存(按单个函数生命周期)
|
|
503
145
|
self._current_agents: Dict[str, Any] = {}
|
|
@@ -509,14 +151,183 @@ class Transpiler:
|
|
|
509
151
|
# 兼容旧字段(不再使用)
|
|
510
152
|
self._current_context_header: str = ""
|
|
511
153
|
self._current_function_id: Optional[int] = None
|
|
154
|
+
# 缓存 compile_commands.json 的解析结果
|
|
155
|
+
self._compile_commands_cache: Optional[List[Dict[str, Any]]] = None
|
|
156
|
+
self._compile_commands_path: Optional[Path] = None
|
|
157
|
+
# 当前函数开始时的 commit id(用于失败回退)
|
|
158
|
+
self._current_function_start_commit: Optional[str] = None
|
|
159
|
+
# 连续修复失败的次数(用于判断是否需要回退)
|
|
160
|
+
self._consecutive_fix_failures: int = 0
|
|
161
|
+
# 每个 Agent 对应的工具调用前的 commit id(用于细粒度检测)
|
|
162
|
+
self._agent_before_commits: Dict[str, Optional[str]] = {}
|
|
163
|
+
|
|
164
|
+
def _find_compile_commands(self) -> Optional[Path]:
|
|
165
|
+
"""
|
|
166
|
+
查找 compile_commands.json 文件。
|
|
167
|
+
搜索顺序:
|
|
168
|
+
1. project_root / compile_commands.json
|
|
169
|
+
2. project_root / build / compile_commands.json
|
|
170
|
+
3. project_root 的父目录及向上查找(最多向上3层)
|
|
171
|
+
"""
|
|
172
|
+
# 首先在 project_root 下查找
|
|
173
|
+
candidates = [
|
|
174
|
+
self.project_root / "compile_commands.json",
|
|
175
|
+
self.project_root / "build" / "compile_commands.json",
|
|
176
|
+
]
|
|
177
|
+
# 向上查找(最多3层)
|
|
178
|
+
current = self.project_root.parent
|
|
179
|
+
for _ in range(3):
|
|
180
|
+
if current and current.exists():
|
|
181
|
+
candidates.append(current / "compile_commands.json")
|
|
182
|
+
current = current.parent
|
|
183
|
+
else:
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
for path in candidates:
|
|
187
|
+
if path.exists() and path.is_file():
|
|
188
|
+
return path.resolve()
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
def _load_compile_commands(self) -> Optional[List[Dict[str, Any]]]:
|
|
192
|
+
"""
|
|
193
|
+
加载 compile_commands.json 文件。
|
|
194
|
+
如果已缓存,直接返回缓存结果。
|
|
195
|
+
"""
|
|
196
|
+
if self._compile_commands_cache is not None:
|
|
197
|
+
return self._compile_commands_cache
|
|
198
|
+
|
|
199
|
+
compile_commands_path = self._find_compile_commands()
|
|
200
|
+
if compile_commands_path is None:
|
|
201
|
+
self._compile_commands_cache = []
|
|
202
|
+
self._compile_commands_path = None
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
with compile_commands_path.open("r", encoding="utf-8") as f:
|
|
207
|
+
data = json.load(f)
|
|
208
|
+
if isinstance(data, list):
|
|
209
|
+
self._compile_commands_cache = data
|
|
210
|
+
self._compile_commands_path = compile_commands_path
|
|
211
|
+
typer.secho(f"[c2rust-transpiler][compile_commands] 已加载: {compile_commands_path} ({len(data)} 条记录)", fg=typer.colors.BLUE)
|
|
212
|
+
return data
|
|
213
|
+
except Exception as e:
|
|
214
|
+
typer.secho(f"[c2rust-transpiler][compile_commands] 加载失败: {compile_commands_path}: {e}", fg=typer.colors.YELLOW)
|
|
215
|
+
self._compile_commands_cache = []
|
|
216
|
+
self._compile_commands_path = None
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
self._compile_commands_cache = []
|
|
220
|
+
self._compile_commands_path = None
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
def _extract_compile_flags(self, c_file_path: Union[str, Path]) -> Optional[str]:
|
|
224
|
+
"""
|
|
225
|
+
从 compile_commands.json 中提取指定 C 文件的编译参数。
|
|
226
|
+
|
|
227
|
+
如果 compile_commands.json 中存在 arguments 字段,则用空格连接该数组并返回。
|
|
228
|
+
如果只有 command 字段,则直接返回 command 字符串。
|
|
229
|
+
|
|
230
|
+
返回格式:
|
|
231
|
+
- 如果存在 arguments:用空格连接的参数字符串,例如 "-I/usr/include -DDEBUG"
|
|
232
|
+
- 如果只有 command:完整的编译命令字符串,例如 "gcc -I/usr/include -DDEBUG file.c"
|
|
233
|
+
|
|
234
|
+
如果未找到或解析失败,返回 None。
|
|
235
|
+
"""
|
|
236
|
+
compile_commands = self._load_compile_commands()
|
|
237
|
+
if not compile_commands:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
# 规范化目标文件路径
|
|
241
|
+
try:
|
|
242
|
+
target_path = Path(c_file_path)
|
|
243
|
+
if not target_path.is_absolute():
|
|
244
|
+
target_path = (self.project_root / target_path).resolve()
|
|
245
|
+
target_path = target_path.resolve()
|
|
246
|
+
except Exception:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
# 查找匹配的编译命令
|
|
250
|
+
for entry in compile_commands:
|
|
251
|
+
if not isinstance(entry, dict):
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
entry_file = entry.get("file")
|
|
255
|
+
if not entry_file:
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
entry_path = Path(entry_file)
|
|
260
|
+
if not entry_path.is_absolute() and entry.get("directory"):
|
|
261
|
+
entry_path = (Path(entry.get("directory")) / entry_path).resolve()
|
|
262
|
+
entry_path = entry_path.resolve()
|
|
263
|
+
|
|
264
|
+
# 路径匹配(支持相对路径和绝对路径)
|
|
265
|
+
if entry_path == target_path:
|
|
266
|
+
# 如果存在 arguments,用空格连接并返回
|
|
267
|
+
arguments = entry.get("arguments")
|
|
268
|
+
if isinstance(arguments, list):
|
|
269
|
+
# 过滤掉空字符串,然后用空格连接
|
|
270
|
+
args = [str(arg) for arg in arguments if arg]
|
|
271
|
+
return " ".join(args) if args else None
|
|
272
|
+
# 如果只有 command,直接返回 command 字符串
|
|
273
|
+
elif entry.get("command"):
|
|
274
|
+
command = entry.get("command", "")
|
|
275
|
+
return command if command else None
|
|
276
|
+
except Exception:
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
return None
|
|
512
280
|
|
|
513
281
|
def _save_progress(self) -> None:
|
|
514
282
|
"""保存进度,使用原子性写入"""
|
|
515
|
-
|
|
283
|
+
write_json(self.progress_path, self.progress)
|
|
284
|
+
|
|
285
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
286
|
+
"""
|
|
287
|
+
从独立的配置文件加载配置。
|
|
288
|
+
如果配置文件不存在,尝试从 progress.json 迁移配置(向后兼容)。
|
|
289
|
+
"""
|
|
290
|
+
config_path = self.data_dir / CONFIG_JSON
|
|
291
|
+
default_config = {"root_symbols": [], "disabled_libraries": [], "additional_notes": ""}
|
|
292
|
+
|
|
293
|
+
# 尝试从配置文件读取
|
|
294
|
+
if config_path.exists():
|
|
295
|
+
config = read_json(config_path, default_config)
|
|
296
|
+
if isinstance(config, dict):
|
|
297
|
+
# 确保包含所有必需的键(向后兼容)
|
|
298
|
+
if "additional_notes" not in config:
|
|
299
|
+
config["additional_notes"] = ""
|
|
300
|
+
return config
|
|
301
|
+
|
|
302
|
+
# 向后兼容:如果配置文件不存在,尝试从 progress.json 迁移
|
|
303
|
+
progress_config = self.progress.get("config", {})
|
|
304
|
+
if progress_config:
|
|
305
|
+
# 迁移配置到独立文件
|
|
306
|
+
migrated_config = {
|
|
307
|
+
"root_symbols": progress_config.get("root_symbols", []),
|
|
308
|
+
"disabled_libraries": progress_config.get("disabled_libraries", []),
|
|
309
|
+
"additional_notes": progress_config.get("additional_notes", ""),
|
|
310
|
+
}
|
|
311
|
+
write_json(config_path, migrated_config)
|
|
312
|
+
typer.secho(f"[c2rust-transpiler][config] 已从 progress.json 迁移配置到 {config_path}", fg=typer.colors.YELLOW)
|
|
313
|
+
# 从 progress.json 中移除 config(可选,保持兼容性)
|
|
314
|
+
# if "config" in self.progress:
|
|
315
|
+
# del self.progress["config"]
|
|
316
|
+
# self._save_progress()
|
|
317
|
+
return migrated_config
|
|
318
|
+
|
|
319
|
+
return default_config
|
|
320
|
+
|
|
321
|
+
def _save_config(self) -> None:
|
|
322
|
+
"""保存配置到独立的配置文件"""
|
|
323
|
+
config_path = self.data_dir / CONFIG_JSON
|
|
324
|
+
config = {
|
|
325
|
+
"root_symbols": self.root_symbols,
|
|
326
|
+
"disabled_libraries": self.disabled_libraries,
|
|
327
|
+
"additional_notes": getattr(self, "additional_notes", ""),
|
|
328
|
+
}
|
|
329
|
+
write_json(config_path, config)
|
|
516
330
|
|
|
517
|
-
# JSONL 模式下不再整体写回 symbol_map;此方法保留占位(兼容旧调用),无操作
|
|
518
|
-
def _save_symbol_map(self) -> None:
|
|
519
|
-
return
|
|
520
331
|
|
|
521
332
|
def _read_source_span(self, rec: FnRecord) -> str:
|
|
522
333
|
"""按起止行读取源码片段(忽略列边界,尽量完整)"""
|
|
@@ -610,12 +421,6 @@ class Transpiler:
|
|
|
610
421
|
typer.secho(f"[c2rust-transpiler][index] 索引构建完成: ids={len(self.fn_index_by_id)} names={len(self.fn_name_to_id)}", fg=typer.colors.BLUE)
|
|
611
422
|
|
|
612
423
|
def _should_skip(self, rec: FnRecord) -> bool:
|
|
613
|
-
# 如果 only 列表非空,则仅处理匹配者
|
|
614
|
-
if self.only:
|
|
615
|
-
if rec.name in self.only or rec.qname in self.only:
|
|
616
|
-
pass
|
|
617
|
-
else:
|
|
618
|
-
return True
|
|
619
424
|
# 已转译的跳过(按源位置与名称唯一性判断,避免同名不同位置的误判)
|
|
620
425
|
if self.symbol_map.has_rec(rec):
|
|
621
426
|
return True
|
|
@@ -676,6 +481,21 @@ class Transpiler:
|
|
|
676
481
|
syms = sorted(list(set(syms)))
|
|
677
482
|
return syms
|
|
678
483
|
|
|
484
|
+
def _append_additional_notes(self, prompt: str) -> str:
|
|
485
|
+
"""
|
|
486
|
+
在提示词末尾追加附加说明(如果存在)。
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
prompt: 原始提示词
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
追加了附加说明的提示词
|
|
493
|
+
"""
|
|
494
|
+
additional_notes = getattr(self, "additional_notes", "")
|
|
495
|
+
if additional_notes and additional_notes.strip():
|
|
496
|
+
return prompt + "\n\n" + "【附加说明(用户自定义)】\n" + additional_notes.strip()
|
|
497
|
+
return prompt
|
|
498
|
+
|
|
679
499
|
def _build_module_selection_prompts(
|
|
680
500
|
self,
|
|
681
501
|
rec: FnRecord,
|
|
@@ -685,13 +505,14 @@ class Transpiler:
|
|
|
685
505
|
) -> Tuple[str, str, str]:
|
|
686
506
|
"""
|
|
687
507
|
返回 (system_prompt, user_prompt, summary_prompt)
|
|
688
|
-
要求 summary 输出
|
|
508
|
+
要求 summary 输出 JSON:
|
|
689
509
|
{
|
|
690
510
|
"module": "src/<path>.rs or module path (e.g., src/foo/mod.rs or src/foo/bar.rs)",
|
|
691
511
|
"rust_signature": "pub fn ...",
|
|
692
512
|
"notes": "optional"
|
|
693
513
|
}
|
|
694
514
|
"""
|
|
515
|
+
is_root = self._is_root_symbol(rec)
|
|
695
516
|
system_prompt = (
|
|
696
517
|
"你是资深Rust工程师,擅长为C/C++函数选择合适的Rust模块位置并产出对应的Rust函数签名。\n"
|
|
697
518
|
"目标:根据提供的C源码、调用者上下文与crate目录结构,为该函数选择合适的Rust模块文件并给出Rust函数签名(不实现)。\n"
|
|
@@ -701,8 +522,27 @@ class Transpiler:
|
|
|
701
522
|
"- 函数接口设计应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型(如 i32/u32/usize、&[T]/&mut [T]、String、Result<T, E> 等),而不是 C 风格类型(如 core::ffi::c_*、libc::c_*);\n"
|
|
702
523
|
"- 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI;\n"
|
|
703
524
|
"- 参数个数与顺序可以保持与 C 一致,但类型设计应优先考虑 Rust 的惯用法和安全性;\n"
|
|
704
|
-
"-
|
|
525
|
+
+ ("- **根符号要求**:此函数是根符号,必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。\n" if is_root else "")
|
|
526
|
+
+ "- **评估是否需要实现**:在规划阶段,请评估此函数是否真的需要实现。以下情况可以跳过实现(设置 skip_implementation 为 true):\n"
|
|
527
|
+
+ " * **已实现的函数**:如果函数已经在目标模块(module)中实现,可以使用 read_code 工具检查目标文件,确认函数已存在且实现正确,则无需重复实现\n"
|
|
528
|
+
+ " * **资源释放类函数**:如文件关闭 fclose、内存释放 free、句柄释放、锁释放等,在 Rust 中通常通过 RAII(Drop trait)自动管理,无需显式实现\n"
|
|
529
|
+
+ " * **已被库替代**:如果函数已被标准库或第三方 crate 替代(lib_replacement 字段已设置),且不需要兼容层,可以跳过实现\n"
|
|
530
|
+
+ " * **空实现或无意义函数**:如果 C 函数本身是空实现、简单返回常量、或仅用于兼容性占位,在 Rust 中可能不需要实现\n"
|
|
531
|
+
+ " * **内联函数或宏**:如果函数在 C 中是内联函数或宏,在 Rust 中可能不需要单独实现\n"
|
|
532
|
+
+ " * **其他不需要实现的情况**:根据具体情况判断,如果函数在 Rust 转译中确实不需要实现,可以跳过\n"
|
|
533
|
+
+ " * 如果设置 skip_implementation 为 true,请在 notes 字段中详细说明原因\n"
|
|
534
|
+
+ "- 仅输出必要信息,避免冗余解释。"
|
|
705
535
|
)
|
|
536
|
+
# 提取编译参数
|
|
537
|
+
compile_flags = self._extract_compile_flags(rec.file)
|
|
538
|
+
compile_flags_section = ""
|
|
539
|
+
if compile_flags:
|
|
540
|
+
compile_flags_section = "\n".join([
|
|
541
|
+
"",
|
|
542
|
+
"C文件编译参数(来自 compile_commands.json):",
|
|
543
|
+
compile_flags,
|
|
544
|
+
])
|
|
545
|
+
|
|
706
546
|
user_prompt = "\n".join([
|
|
707
547
|
"请阅读以下上下文并准备总结:",
|
|
708
548
|
f"- 函数标识: id={rec.id}, name={rec.name}, qualified={rec.qname}",
|
|
@@ -722,7 +562,10 @@ class Transpiler:
|
|
|
722
562
|
"",
|
|
723
563
|
"库替代上下文(若存在):",
|
|
724
564
|
json.dumps(getattr(rec, "lib_replacement", None), ensure_ascii=False, indent=2),
|
|
565
|
+
compile_flags_section,
|
|
725
566
|
"",
|
|
567
|
+
*([f"禁用库列表(禁止在实现中使用这些库):{', '.join(self.disabled_libraries)}"] if self.disabled_libraries else []),
|
|
568
|
+
*([""] if self.disabled_libraries else []),
|
|
726
569
|
"当前crate目录结构(部分):",
|
|
727
570
|
"<CRATE_TREE>",
|
|
728
571
|
crate_tree,
|
|
@@ -730,45 +573,78 @@ class Transpiler:
|
|
|
730
573
|
"",
|
|
731
574
|
"为避免完整读取体积较大的符号表,你也可以使用工具 read_symbols 按需获取指定符号记录:",
|
|
732
575
|
"- 工具: read_symbols",
|
|
733
|
-
"- 参数示例(
|
|
734
|
-
f" symbols_file: \"{(self.data_dir / 'symbols.jsonl').resolve()}\"",
|
|
735
|
-
"
|
|
736
|
-
"
|
|
576
|
+
"- 参数示例(JSON):",
|
|
577
|
+
f" {{\"symbols_file\": \"{(self.data_dir / 'symbols.jsonl').resolve()}\", \"symbols\": [\"符号1\", \"符号2\"]}}",
|
|
578
|
+
"",
|
|
579
|
+
"**重要:检查函数是否已实现**",
|
|
580
|
+
"在确定目标模块(module)后,请使用 read_code 工具检查该模块文件,确认函数是否已经实现:",
|
|
581
|
+
"- 工具: read_code",
|
|
582
|
+
"- 参数示例(JSON):",
|
|
583
|
+
" {\"file_path\": \"<目标模块路径>\"}",
|
|
584
|
+
"- 如果函数已经在目标模块中实现,且实现正确,可以设置 skip_implementation 为 true,并在 notes 中说明 \"函数已在目标模块中实现\"",
|
|
737
585
|
"",
|
|
738
586
|
"如果理解完毕,请进入总结阶段。",
|
|
739
587
|
])
|
|
740
588
|
summary_prompt = (
|
|
741
|
-
"请仅输出一个 <SUMMARY> 块,块内必须且只包含一个
|
|
742
|
-
"允许字段(
|
|
743
|
-
'- module: "<绝对路径>/src/xxx.rs 或 <绝对路径>/src/xxx/mod.rs;或相对路径 src/xxx.rs / src/xxx/mod.rs"\n'
|
|
744
|
-
'- rust_signature: "pub fn xxx(...)->..."\n'
|
|
745
|
-
'-
|
|
589
|
+
"请仅输出一个 <SUMMARY> 块,块内必须且只包含一个 JSON 对象,不得包含其它内容。\n"
|
|
590
|
+
"允许字段(JSON 对象):\n"
|
|
591
|
+
'- "module": "<绝对路径>/src/xxx.rs 或 <绝对路径>/src/xxx/mod.rs;或相对路径 src/xxx.rs / src/xxx/mod.rs"\n'
|
|
592
|
+
'- "rust_signature": "pub fn xxx(...)->..."\n'
|
|
593
|
+
'- "skip_implementation": bool // 可选,如果为 true,表示此函数不需要实现,可以直接标记为已实现\n'
|
|
594
|
+
'- "notes": "可选说明(若有上下文缺失或风险点,请在此列出;如果 skip_implementation 为 true,必须在此说明原因)"\n'
|
|
746
595
|
"注意:\n"
|
|
747
596
|
"- module 必须位于 crate 的 src/ 目录下,接受绝对路径或以 src/ 开头的相对路径;尽量选择已有文件;如需新建文件,给出合理路径;\n"
|
|
748
597
|
"- rust_signature 应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型和惯用法,而不是 C 风格类型。\n"
|
|
598
|
+
"- **评估是否需要实现**:请仔细评估此函数是否真的需要实现。以下情况可以设置 skip_implementation 为 true:\n"
|
|
599
|
+
+ " * **已实现的函数**:如果函数已经在目标模块(module)中实现,可以使用 read_code 工具检查目标文件,确认函数已存在且实现正确,则无需重复实现\n"
|
|
600
|
+
+ " * **资源释放类函数**:如文件关闭 fclose、内存释放 free、句柄释放、锁释放等,在 Rust 中通常通过 RAII(Drop trait)自动管理,无需显式实现\n"
|
|
601
|
+
+ " * **已被库替代**:如果函数已被标准库或第三方 crate 替代(lib_replacement 字段已设置),且不需要兼容层,可以跳过实现\n"
|
|
602
|
+
+ " * **空实现或无意义函数**:如果 C 函数本身是空实现、简单返回常量、或仅用于兼容性占位,在 Rust 中可能不需要实现\n"
|
|
603
|
+
+ " * **内联函数或宏**:如果函数在 C 中是内联函数或宏,在 Rust 中可能不需要单独实现\n"
|
|
604
|
+
+ " * **其他不需要实现的情况**:根据具体情况判断,如果函数在 Rust 转译中确实不需要实现,可以跳过\n"
|
|
605
|
+
+ " * **重要**:如果设置 skip_implementation 为 true,必须在 notes 字段中详细说明原因,例如:\n"
|
|
606
|
+
+ " - \"函数已在目标模块中实现\"\n"
|
|
607
|
+
+ " - \"通过 RAII 自动管理,无需显式实现\"\n"
|
|
608
|
+
+ " - \"已被标准库 std::xxx 替代,无需实现\"\n"
|
|
609
|
+
+ " - \"空实现函数,在 Rust 中不需要\"\n"
|
|
610
|
+
+ " - \"内联函数,已在调用处展开,无需单独实现\"\n"
|
|
611
|
+
+ "- 如果函数确实需要实现,则不要设置 skip_implementation 或设置为 false\n"
|
|
749
612
|
"- 类型设计原则:\n"
|
|
750
613
|
" * 基本类型:优先使用 i32/u32/i64/u64/isize/usize/f32/f64 等原生 Rust 类型,而不是 core::ffi::c_* 或 libc::c_*;\n"
|
|
751
614
|
" * 指针/引用:优先使用引用 &T/&mut T 或切片 &[T]/&mut [T],而非原始指针 *const T/*mut T;仅在必要时使用原始指针;\n"
|
|
752
615
|
" * 字符串:优先使用 String、&str 而非 *const c_char/*mut c_char;\n"
|
|
753
616
|
" * 错误处理:考虑使用 Result<T, E> 而非 C 风格的错误码;\n"
|
|
754
617
|
" * 参数个数与顺序可以保持与 C 一致,但类型应优先考虑 Rust 的惯用法、安全性和可读性;\n"
|
|
755
|
-
"-
|
|
618
|
+
+ ("- **根符号要求**:此函数是根符号,rust_signature 必须包含 `pub` 关键字,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。\n" if is_root else "")
|
|
619
|
+
+ "- 函数签名应包含可见性修饰(pub)与函数名;类型应为 Rust 最佳实践的选择,而非简单映射 C 类型。\n"
|
|
756
620
|
"- 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI。\n"
|
|
757
|
-
"
|
|
758
|
-
"
|
|
621
|
+
"请严格按以下格式输出(JSON格式,支持jsonnet语法如尾随逗号、注释、|||分隔符多行字符串等):\n"
|
|
622
|
+
"示例1(正常函数):\n"
|
|
623
|
+
"<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"notes\": \"...\"\n}\n</SUMMARY>\n"
|
|
624
|
+
"示例2(已实现的函数,可跳过实现):\n"
|
|
625
|
+
"<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"函数已在目标模块中实现\"\n}\n</SUMMARY>\n"
|
|
626
|
+
"示例3(不需要实现的函数,可跳过实现):\n"
|
|
627
|
+
"<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"通过 RAII 自动管理,无需显式实现\"\n}\n</SUMMARY>\n"
|
|
628
|
+
"示例4(已被库替代,可跳过实现):\n"
|
|
629
|
+
"<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"已被标准库 std::xxx 替代,无需实现\"\n}\n</SUMMARY>\n"
|
|
630
|
+
"示例5(空实现函数,可跳过实现):\n"
|
|
631
|
+
"<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"C 函数为空实现,在 Rust 中不需要\"\n}\n</SUMMARY>"
|
|
759
632
|
)
|
|
633
|
+
# 在 user_prompt 和 summary_prompt 中追加附加说明(system_prompt 通常不需要)
|
|
634
|
+
user_prompt = self._append_additional_notes(user_prompt)
|
|
635
|
+
summary_prompt = self._append_additional_notes(summary_prompt)
|
|
760
636
|
return system_prompt, user_prompt, summary_prompt
|
|
761
637
|
|
|
762
|
-
def _plan_module_and_signature(self, rec: FnRecord, c_code: str) -> Tuple[str, str]:
|
|
763
|
-
"""调用 Agent 选择模块与签名,返回 (module_path, rust_signature),若格式不满足将自动重试直到满足"""
|
|
764
|
-
crate_tree =
|
|
638
|
+
def _plan_module_and_signature(self, rec: FnRecord, c_code: str) -> Tuple[str, str, bool]:
|
|
639
|
+
"""调用 Agent 选择模块与签名,返回 (module_path, rust_signature, skip_implementation),若格式不满足将自动重试直到满足"""
|
|
640
|
+
crate_tree = dir_tree(self.crate_dir)
|
|
765
641
|
callees_ctx = self._collect_callees_context(rec)
|
|
766
642
|
sys_p, usr_p, base_sum_p = self._build_module_selection_prompts(rec, c_code, callees_ctx, crate_tree)
|
|
767
643
|
|
|
768
644
|
def _validate(meta: Any) -> Tuple[bool, str]:
|
|
769
645
|
"""基本格式检查,仅验证字段存在性,不做硬编码规则校验"""
|
|
770
646
|
if not isinstance(meta, dict) or not meta:
|
|
771
|
-
return False, "未解析到有效的 <SUMMARY
|
|
647
|
+
return False, "未解析到有效的 <SUMMARY> 中的 JSON 对象"
|
|
772
648
|
module = meta.get("module")
|
|
773
649
|
rust_sig = meta.get("rust_signature")
|
|
774
650
|
if not isinstance(module, str) or not module.strip():
|
|
@@ -802,8 +678,8 @@ class Transpiler:
|
|
|
802
678
|
base_sum_p
|
|
803
679
|
+ "\n\n[格式检查失败,必须重试]\n"
|
|
804
680
|
+ f"- 失败原因:{reason}\n"
|
|
805
|
-
+ "- 仅输出一个 <SUMMARY>
|
|
806
|
-
+ '-
|
|
681
|
+
+ "- 仅输出一个 <SUMMARY> 块;块内直接包含 JSON 对象(不需要额外的标签);\n"
|
|
682
|
+
+ '- JSON 对象必须包含字段:module、rust_signature。\n'
|
|
807
683
|
)
|
|
808
684
|
|
|
809
685
|
attempt = 0
|
|
@@ -813,59 +689,66 @@ class Transpiler:
|
|
|
813
689
|
use_direct_model = False # 标记是否使用直接模型调用
|
|
814
690
|
agent = None # 在循环外声明,以便重试时复用
|
|
815
691
|
|
|
692
|
+
# 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
|
|
693
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
816
694
|
while plan_max_retries_val == 0 or attempt < plan_max_retries_val:
|
|
817
695
|
attempt += 1
|
|
818
696
|
sum_p = base_sum_p if attempt == 1 else _retry_sum_prompt(last_reason)
|
|
819
697
|
|
|
820
698
|
# 第一次创建 Agent,后续重试时复用(如果使用直接模型调用)
|
|
699
|
+
# 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
|
|
821
700
|
if agent is None or not use_direct_model:
|
|
701
|
+
# 获取函数信息用于 Agent name
|
|
702
|
+
fn_name = rec.qname or rec.name or f"fn_{rec.id}"
|
|
703
|
+
agent_name = f"C2Rust-Function-Planner({fn_name})"
|
|
822
704
|
agent = Agent(
|
|
823
705
|
system_prompt=sys_p,
|
|
824
|
-
name=
|
|
706
|
+
name=agent_name,
|
|
825
707
|
model_group=self.llm_group,
|
|
826
708
|
summary_prompt=sum_p,
|
|
827
709
|
need_summary=True,
|
|
828
710
|
auto_complete=True,
|
|
829
|
-
use_tools=["execute_script", "read_code", "
|
|
830
|
-
plan=False,
|
|
711
|
+
use_tools=["execute_script", "read_code", "read_symbols"],
|
|
831
712
|
non_interactive=self.non_interactive,
|
|
832
713
|
use_methodology=False,
|
|
833
714
|
use_analysis=False,
|
|
834
|
-
disable_file_edit=True,
|
|
835
715
|
)
|
|
716
|
+
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
717
|
+
agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
718
|
+
agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
719
|
+
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
720
|
+
agent_id = id(agent)
|
|
721
|
+
agent_key = f"agent_{agent_id}"
|
|
722
|
+
initial_commit = self._get_crate_commit_hash()
|
|
723
|
+
if initial_commit:
|
|
724
|
+
self._agent_before_commits[agent_key] = initial_commit
|
|
836
725
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
726
|
+
if use_direct_model:
|
|
727
|
+
# 格式校验失败后,直接调用模型接口
|
|
728
|
+
# 构造包含摘要提示词和具体错误信息的完整提示
|
|
729
|
+
error_guidance = ""
|
|
730
|
+
if last_reason and last_reason != "未知错误":
|
|
731
|
+
if "JSON解析失败" in last_reason:
|
|
732
|
+
error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出的JSON格式正确,包括正确的引号、逗号、大括号等。JSON 对象必须包含字段:module(字符串)、rust_signature(字符串)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
|
|
733
|
+
else:
|
|
734
|
+
error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内直接包含 JSON 对象(不需要额外的标签);JSON 对象必须包含字段:module(字符串)、rust_signature(字符串)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
|
|
840
735
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
#
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出的YAML格式正确,包括正确的缩进、引号、冒号等。YAML 对象必须包含字段:module(字符串)、rust_signature(字符串)。"
|
|
848
|
-
else:
|
|
849
|
-
error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内仅包含单个 <yaml> 对象;YAML 对象必须包含字段:module(字符串)、rust_signature(字符串)。"
|
|
850
|
-
|
|
851
|
-
full_prompt = f"{usr_p}{error_guidance}\n\n{sum_p}"
|
|
852
|
-
try:
|
|
853
|
-
response = agent.model.chat_until_success(full_prompt) # type: ignore
|
|
854
|
-
summary = response
|
|
855
|
-
except Exception as e:
|
|
856
|
-
typer.secho(f"[c2rust-transpiler][plan] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
|
|
857
|
-
summary = agent.run(usr_p)
|
|
858
|
-
else:
|
|
859
|
-
# 第一次使用 run(),让 Agent 完整运行(可能使用工具)
|
|
736
|
+
full_prompt = f"{usr_p}{error_guidance}\n\n{sum_p}"
|
|
737
|
+
try:
|
|
738
|
+
response = agent.model.chat_until_success(full_prompt) # type: ignore
|
|
739
|
+
summary = response
|
|
740
|
+
except Exception as e:
|
|
741
|
+
typer.secho(f"[c2rust-transpiler][plan] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
|
|
860
742
|
summary = agent.run(usr_p)
|
|
861
|
-
|
|
862
|
-
|
|
743
|
+
else:
|
|
744
|
+
# 第一次使用 run(),让 Agent 完整运行(可能使用工具)
|
|
745
|
+
summary = agent.run(usr_p)
|
|
863
746
|
|
|
864
|
-
meta, parse_error =
|
|
747
|
+
meta, parse_error = extract_json_from_summary(str(summary or ""))
|
|
865
748
|
if parse_error:
|
|
866
|
-
#
|
|
867
|
-
typer.secho(f"[c2rust-transpiler][plan]
|
|
868
|
-
last_reason = f"
|
|
749
|
+
# JSON解析失败,将错误信息反馈给模型
|
|
750
|
+
typer.secho(f"[c2rust-transpiler][plan] JSON解析失败: {parse_error}", fg=typer.colors.YELLOW)
|
|
751
|
+
last_reason = f"JSON解析失败: {parse_error}"
|
|
869
752
|
use_direct_model = True
|
|
870
753
|
# 解析失败,继续重试
|
|
871
754
|
continue
|
|
@@ -874,13 +757,21 @@ class Transpiler:
|
|
|
874
757
|
if ok:
|
|
875
758
|
module = str(meta.get("module") or "").strip()
|
|
876
759
|
rust_sig = str(meta.get("rust_signature") or "").strip()
|
|
877
|
-
|
|
878
|
-
|
|
760
|
+
skip_impl = bool(meta.get("skip_implementation") is True)
|
|
761
|
+
if skip_impl:
|
|
762
|
+
notes = str(meta.get("notes") or "")
|
|
763
|
+
typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试成功: 模块={module}, 签名={rust_sig}, 跳过实现={skip_impl}", fg=typer.colors.GREEN)
|
|
764
|
+
if notes:
|
|
765
|
+
typer.secho(f"[c2rust-transpiler][plan] 跳过实现原因: {notes}", fg=typer.colors.CYAN)
|
|
766
|
+
else:
|
|
767
|
+
typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试成功: 模块={module}, 签名={rust_sig}", fg=typer.colors.GREEN)
|
|
768
|
+
return module, rust_sig, skip_impl
|
|
879
769
|
else:
|
|
880
770
|
typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试失败: {reason}", fg=typer.colors.YELLOW)
|
|
881
771
|
last_reason = reason
|
|
882
772
|
# 格式校验失败,后续重试使用直接模型调用
|
|
883
773
|
use_direct_model = True
|
|
774
|
+
|
|
884
775
|
# 规划超出重试上限:回退到兜底方案(默认模块 src/ffi.rs + 简单占位签名)
|
|
885
776
|
# 注意:如果 plan_max_retries_val == 0(无限重试),理论上不应该到达这里
|
|
886
777
|
try:
|
|
@@ -890,7 +781,7 @@ class Transpiler:
|
|
|
890
781
|
fallback_module = "src/ffi.rs"
|
|
891
782
|
fallback_sig = f"pub fn {rec.name or ('fn_' + str(rec.id))}()"
|
|
892
783
|
typer.secho(f"[c2rust-transpiler][plan] 超出规划重试上限({plan_max_retries_val if plan_max_retries_val > 0 else '无限'}),回退到兜底: module={fallback_module}, signature={fallback_sig}", fg=typer.colors.YELLOW)
|
|
893
|
-
return fallback_module, fallback_sig
|
|
784
|
+
return fallback_module, fallback_sig, False
|
|
894
785
|
|
|
895
786
|
def _update_progress_current(self, rec: FnRecord, module: str, rust_sig: str) -> None:
|
|
896
787
|
self.progress["current"] = {
|
|
@@ -938,8 +829,10 @@ class Transpiler:
|
|
|
938
829
|
|
|
939
830
|
# 汇总上下文头部,供后续复用时拼接
|
|
940
831
|
callees_ctx = self._collect_callees_context(rec)
|
|
941
|
-
crate_tree =
|
|
832
|
+
crate_tree = dir_tree(self.crate_dir)
|
|
942
833
|
librep_ctx = rec.lib_replacement if isinstance(rec.lib_replacement, dict) else None
|
|
834
|
+
# 提取编译参数
|
|
835
|
+
compile_flags = self._extract_compile_flags(rec.file)
|
|
943
836
|
|
|
944
837
|
header_lines = [
|
|
945
838
|
"【当前函数上下文(复用Agent专用)】",
|
|
@@ -960,12 +853,21 @@ class Transpiler:
|
|
|
960
853
|
"",
|
|
961
854
|
"库替代上下文(若有):",
|
|
962
855
|
json.dumps(librep_ctx, ensure_ascii=False, indent=2),
|
|
856
|
+
]
|
|
857
|
+
# 添加编译参数(如果存在)
|
|
858
|
+
if compile_flags:
|
|
859
|
+
header_lines.extend([
|
|
860
|
+
"",
|
|
861
|
+
"C文件编译参数(来自 compile_commands.json):",
|
|
862
|
+
compile_flags,
|
|
863
|
+
])
|
|
864
|
+
header_lines.extend([
|
|
963
865
|
"",
|
|
964
866
|
"crate 目录结构(部分):",
|
|
965
867
|
"<CRATE_TREE>",
|
|
966
868
|
crate_tree,
|
|
967
869
|
"</CRATE_TREE>",
|
|
968
|
-
]
|
|
870
|
+
])
|
|
969
871
|
# 精简头部(后续复用)
|
|
970
872
|
compact_lines = [
|
|
971
873
|
"【函数上下文简要(复用)】",
|
|
@@ -979,37 +881,130 @@ class Transpiler:
|
|
|
979
881
|
self._current_context_compact_header = "\n".join(compact_lines)
|
|
980
882
|
self._current_context_full_sent = False
|
|
981
883
|
|
|
982
|
-
# 初始化一次代码生成Agent(CodeAgent),单个函数生命周期内复用
|
|
983
|
-
# 该Agent用于代码生成和修复,启用方法论、分析和强制记忆功能
|
|
984
|
-
self._current_agents[f"code_agent::{rec.id}"] = CodeAgent(
|
|
985
|
-
need_summary=False,
|
|
986
|
-
non_interactive=self.non_interactive,
|
|
987
|
-
plan=False,
|
|
988
|
-
model_group=self.llm_group,
|
|
989
|
-
use_methodology=True,
|
|
990
|
-
use_analysis=True,
|
|
991
|
-
force_save_memory=True, # 强制使用记忆功能
|
|
992
|
-
)
|
|
993
884
|
|
|
994
|
-
def
|
|
885
|
+
def _on_before_tool_call(self, agent: Any, current_response=None, **kwargs) -> None:
|
|
886
|
+
"""
|
|
887
|
+
工具调用前的事件处理器,用于记录工具调用前的 commit id。
|
|
888
|
+
|
|
889
|
+
在每次工具调用前记录当前的 commit,以便在工具调用后检测到问题时能够回退。
|
|
890
|
+
"""
|
|
891
|
+
try:
|
|
892
|
+
# 只关注可能修改代码的工具
|
|
893
|
+
# 注意:在 BEFORE_TOOL_CALL 时,工具还未执行,无法获取工具名称
|
|
894
|
+
# 但我们可以在 AFTER_TOOL_CALL 时检查工具名称,这里先记录 commit
|
|
895
|
+
agent_id = id(agent)
|
|
896
|
+
agent_key = f"agent_{agent_id}"
|
|
897
|
+
current_commit = self._get_crate_commit_hash()
|
|
898
|
+
if current_commit:
|
|
899
|
+
# 记录工具调用前的 commit(如果之前没有记录,或者 commit 已变化)
|
|
900
|
+
if agent_key not in self._agent_before_commits or self._agent_before_commits[agent_key] != current_commit:
|
|
901
|
+
self._agent_before_commits[agent_key] = current_commit
|
|
902
|
+
except Exception as e:
|
|
903
|
+
# 事件处理器异常不应影响主流程
|
|
904
|
+
typer.secho(f"[c2rust-transpiler][test-detection] BEFORE_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
|
|
905
|
+
|
|
906
|
+
def _on_after_tool_call(self, agent: Any, current_response=None, need_return=None, tool_prompt=None, **kwargs) -> None:
|
|
907
|
+
"""
|
|
908
|
+
工具调用后的事件处理器,用于细粒度检测测试代码删除。
|
|
909
|
+
|
|
910
|
+
在每次工具调用后立即检测,如果检测到测试代码被错误删除,立即回退。
|
|
911
|
+
"""
|
|
912
|
+
try:
|
|
913
|
+
# 只检测编辑文件的工具调用
|
|
914
|
+
last_tool = agent.get_user_data("__last_executed_tool__") if hasattr(agent, "get_user_data") else None
|
|
915
|
+
if not last_tool:
|
|
916
|
+
return
|
|
917
|
+
|
|
918
|
+
# 只关注可能修改代码的工具
|
|
919
|
+
edit_tools = {"edit_file", "rewrite_file", "apply_patch"}
|
|
920
|
+
if last_tool not in edit_tools:
|
|
921
|
+
return
|
|
922
|
+
|
|
923
|
+
# 获取该 Agent 对应的工具调用前的 commit id
|
|
924
|
+
agent_id = id(agent)
|
|
925
|
+
agent_key = f"agent_{agent_id}"
|
|
926
|
+
before_commit = self._agent_before_commits.get(agent_key)
|
|
927
|
+
|
|
928
|
+
# 如果没有 commit 信息,无法检测
|
|
929
|
+
if not before_commit:
|
|
930
|
+
return
|
|
931
|
+
|
|
932
|
+
# 检测测试代码删除
|
|
933
|
+
from jarvis.jarvis_c2rust.utils import detect_test_deletion, ask_llm_about_test_deletion
|
|
934
|
+
|
|
935
|
+
detection_result = detect_test_deletion("[c2rust-transpiler]")
|
|
936
|
+
if not detection_result:
|
|
937
|
+
# 没有检测到删除,更新 commit 记录
|
|
938
|
+
current_commit = self._get_crate_commit_hash()
|
|
939
|
+
if current_commit and current_commit != before_commit:
|
|
940
|
+
self._agent_before_commits[agent_key] = current_commit
|
|
941
|
+
return
|
|
942
|
+
|
|
943
|
+
typer.secho("[c2rust-transpiler][test-detection] 检测到可能错误删除了测试代码标记(工具调用后检测)", fg=typer.colors.YELLOW)
|
|
944
|
+
|
|
945
|
+
# 询问 LLM 是否合理
|
|
946
|
+
need_reset = ask_llm_about_test_deletion(detection_result, agent, "[c2rust-transpiler]")
|
|
947
|
+
|
|
948
|
+
if need_reset:
|
|
949
|
+
typer.secho(f"[c2rust-transpiler][test-detection] LLM 确认删除不合理,正在回退到 commit: {before_commit}", fg=typer.colors.RED)
|
|
950
|
+
if self._reset_to_commit(before_commit):
|
|
951
|
+
typer.secho("[c2rust-transpiler][test-detection] 已回退到之前的 commit(工具调用后检测)", fg=typer.colors.GREEN)
|
|
952
|
+
# 回退后,保持之前的 commit 记录
|
|
953
|
+
self._agent_before_commits[agent_key] = before_commit
|
|
954
|
+
# 在 agent 的 session 中添加提示,告知修改被撤销
|
|
955
|
+
if hasattr(agent, "session") and hasattr(agent.session, "prompt"):
|
|
956
|
+
agent.session.prompt += "\n\n⚠️ 修改被撤销:检测到测试代码被错误删除,已回退到之前的版本。\n"
|
|
957
|
+
else:
|
|
958
|
+
typer.secho("[c2rust-transpiler][test-detection] 回退失败", fg=typer.colors.RED)
|
|
959
|
+
else:
|
|
960
|
+
# LLM 认为删除合理,更新 commit 记录
|
|
961
|
+
current_commit = self._get_crate_commit_hash()
|
|
962
|
+
if current_commit and current_commit != before_commit:
|
|
963
|
+
self._agent_before_commits[agent_key] = current_commit
|
|
964
|
+
except Exception as e:
|
|
965
|
+
# 事件处理器异常不应影响主流程
|
|
966
|
+
typer.secho(f"[c2rust-transpiler][test-detection] AFTER_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
|
|
967
|
+
|
|
968
|
+
def _get_code_agent(self) -> CodeAgent:
|
|
995
969
|
"""
|
|
996
|
-
|
|
997
|
-
|
|
970
|
+
获取代码生成/修复Agent(CodeAgent)。若未初始化,则按当前函数id创建。
|
|
971
|
+
统一用于代码生成和修复,启用方法论和分析功能以提供更好的代码质量。
|
|
972
|
+
注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表。
|
|
973
|
+
提示:代码生成遵循 TDD(测试驱动开发)方法,通过提示词指导 Agent 先写测试再写实现。
|
|
998
974
|
"""
|
|
999
975
|
fid = self._current_function_id
|
|
1000
976
|
key = f"code_agent::{fid}" if fid is not None else "code_agent::default"
|
|
1001
977
|
agent = self._current_agents.get(key)
|
|
1002
978
|
if agent is None:
|
|
1003
|
-
#
|
|
979
|
+
# 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
|
|
980
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
981
|
+
# 统一启用方法论和分析功能,提供更好的代码生成和修复能力
|
|
982
|
+
# 获取函数信息用于 Agent name
|
|
983
|
+
fn_name = ""
|
|
984
|
+
if fid is not None:
|
|
985
|
+
rec = self.fn_index_by_id.get(fid)
|
|
986
|
+
if rec:
|
|
987
|
+
fn_name = rec.qname or rec.name or f"fn_{fid}"
|
|
988
|
+
agent_name = "C2Rust-CodeAgent" + (f"({fn_name})" if fn_name else "")
|
|
1004
989
|
agent = CodeAgent(
|
|
990
|
+
name=agent_name,
|
|
1005
991
|
need_summary=False,
|
|
1006
992
|
non_interactive=self.non_interactive,
|
|
1007
|
-
plan=False,
|
|
1008
993
|
model_group=self.llm_group,
|
|
994
|
+
append_tools="read_symbols,methodology",
|
|
1009
995
|
use_methodology=True,
|
|
1010
996
|
use_analysis=True,
|
|
1011
|
-
force_save_memory=
|
|
997
|
+
force_save_memory=False,
|
|
1012
998
|
)
|
|
999
|
+
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
1000
|
+
agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
1001
|
+
agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
1002
|
+
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
1003
|
+
agent_id = id(agent)
|
|
1004
|
+
agent_key = f"agent_{agent_id}"
|
|
1005
|
+
initial_commit = self._get_crate_commit_hash()
|
|
1006
|
+
if initial_commit:
|
|
1007
|
+
self._agent_before_commits[agent_key] = initial_commit
|
|
1013
1008
|
self._current_agents[key] = agent
|
|
1014
1009
|
return agent
|
|
1015
1010
|
|
|
@@ -1032,93 +1027,65 @@ class Transpiler:
|
|
|
1032
1027
|
|
|
1033
1028
|
# ========= 代码生成与修复 =========
|
|
1034
1029
|
|
|
1035
|
-
def
|
|
1030
|
+
def _is_root_symbol(self, rec: FnRecord) -> bool:
|
|
1031
|
+
"""判断函数是否为根符号(排除 main)"""
|
|
1032
|
+
if not self.root_symbols:
|
|
1033
|
+
return False
|
|
1034
|
+
# 检查函数名或限定名是否在根符号列表中
|
|
1035
|
+
return (rec.name in self.root_symbols) or (rec.qname in self.root_symbols)
|
|
1036
|
+
|
|
1037
|
+
def _build_generate_impl_prompt(
|
|
1038
|
+
self, rec: FnRecord, c_code: str, module: str, rust_sig: str, unresolved: List[str]
|
|
1039
|
+
) -> str:
|
|
1036
1040
|
"""
|
|
1037
|
-
|
|
1038
|
-
|
|
1041
|
+
构建代码生成提示词。
|
|
1042
|
+
|
|
1043
|
+
返回完整的提示词字符串。
|
|
1039
1044
|
"""
|
|
1040
1045
|
symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
|
|
1046
|
+
is_root = self._is_root_symbol(rec)
|
|
1041
1047
|
requirement_lines = [
|
|
1042
|
-
f"目标:在
|
|
1043
|
-
"
|
|
1044
|
-
f"
|
|
1045
|
-
f"
|
|
1046
|
-
"
|
|
1047
|
-
"
|
|
1048
|
-
"
|
|
1049
|
-
|
|
1050
|
-
"
|
|
1051
|
-
"
|
|
1052
|
-
"
|
|
1048
|
+
f"目标:在 {module} 中,使用 TDD 方法为 C 函数 {rec.qname or rec.name} 生成 Rust 实现。",
|
|
1049
|
+
f"函数签名:{rust_sig}",
|
|
1050
|
+
f"crate 目录:{self.crate_dir.resolve()}",
|
|
1051
|
+
f"C 工程目录:{self.project_root.resolve()}",
|
|
1052
|
+
*(["根符号要求:必须使用 `pub` 关键字,模块必须在 src/lib.rs 中导出"] if is_root else []),
|
|
1053
|
+
"",
|
|
1054
|
+
"【TDD 流程】",
|
|
1055
|
+
"1. Red:先写测试(#[cfg(test)] mod tests),基于 C 函数行为设计测试用例",
|
|
1056
|
+
"2. Green:编写实现使测试通过,确保与 C 语义等价",
|
|
1057
|
+
"3. Refactor:优化代码,保持测试通过",
|
|
1058
|
+
"",
|
|
1059
|
+
"【核心要求】",
|
|
1060
|
+
"- 先写测试再写实现,测试必须可编译通过",
|
|
1061
|
+
"- 禁止使用 todo!/unimplemented!,必须实现完整功能",
|
|
1062
|
+
"- 使用 Rust 原生类型(i32/u32、&str/String、&[T]/&mut [T]、Result<T,E>),避免 C 风格类型",
|
|
1063
|
+
"- 禁止使用 extern \"C\",使用标准 Rust 调用约定",
|
|
1064
|
+
"- 保持最小变更,避免无关重构",
|
|
1065
|
+
"- 注释使用中文,禁止 use ...::* 通配导入",
|
|
1066
|
+
"- 资源释放类函数(fclose/free 等)可通过 RAII 自动管理,提供空实现并在文档中说明",
|
|
1067
|
+
*([f"- 禁用库:{', '.join(self.disabled_libraries)}"] if self.disabled_libraries else []),
|
|
1068
|
+
"",
|
|
1069
|
+
"【依赖处理】",
|
|
1070
|
+
"- 检查依赖函数是否已实现,未实现的需一并补齐(遵循 TDD:先测试后实现)",
|
|
1071
|
+
"- 使用 read_symbols/read_code 获取 C 源码",
|
|
1072
|
+
"- 优先处理底层依赖,确保所有测试通过",
|
|
1053
1073
|
"",
|
|
1054
|
-
"
|
|
1055
|
-
"-
|
|
1056
|
-
"
|
|
1057
|
-
" * 指针/引用:优先使用引用 &T/&mut T 或切片 &[T]/&mut [T],而非原始指针 *const T/*mut T;仅在必要时使用原始指针;",
|
|
1058
|
-
" * 字符串:优先使用 String、&str 而非 *const c_char/*mut c_char;",
|
|
1059
|
-
" * 错误处理:如适用,考虑使用 Result<T, E> 而非 C 风格的错误码;",
|
|
1060
|
-
" * 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI;",
|
|
1061
|
-
"- 保持最小变更,避免无关重构与格式化;禁止批量重排/重命名/移动文件;",
|
|
1062
|
-
"- 命名遵循Rust惯例(函数/模块蛇形命名),公共API使用pub;",
|
|
1063
|
-
"- 优先使用安全Rust;如需unsafe,将范围最小化并添加注释说明原因与SAFETY保证;",
|
|
1064
|
-
"- 错误处理:遵循 C 语义保持等价行为(逻辑一致性),但在类型设计上优先使用 Rust 惯用法;避免 panic!/unwrap();",
|
|
1065
|
-
"- 实现中禁止使用 todo!/unimplemented! 占位;对于尚未实现的被调符号,应基于其 C 源码补齐等价 Rust 实现;",
|
|
1066
|
-
"- 返回值必须与 C 语义等价,不得使用占位返回或随意默认值;避免 panic!/todo!/unimplemented!;",
|
|
1067
|
-
"- 若依赖未实现符号,请通过 read_symbols/read_code 获取其 C 源码并生成等价的 Rust 实现(可放置在同一模块或合理模块),而不是使用 todo!;",
|
|
1068
|
-
"- 文档:为新增函数添加简要文档注释,注明来源C函数与意图;",
|
|
1069
|
-
"- 导入:禁止使用 use ...::* 通配;仅允许精确导入所需符号",
|
|
1070
|
-
"- 依赖管理:如引入新的外部 crate 或需要启用 feature,请同步更新 Cargo.toml 的 [dependencies]/[dev-dependencies]/[features],避免未声明依赖导致构建失败;版本号可使用兼容范围(如 ^x.y)或默认值;",
|
|
1071
|
-
"- 测试生成:若函数可测试(无需外部环境/IO/网络/全局状态),在同文件添加 #[cfg(test)] mod tests 并编写至少一个可编译的单元测试(建议命名为 test_<函数名>_basic);测试仅调用函数,避免使用 panic!/todo!/unimplemented!;unsafe 函数以 unsafe 调用;必要时使用占位参数(如 0、core::ptr::null()/null_mut()、默认值)以保证通过。",
|
|
1072
|
-
"- 测试设计文档:在测试函数顶部使用文档注释(///)简要说明测试用例设计,包括:输入构造、预置条件、期望行为(或成功执行)、边界/异常不作为否决项;注释内容仅用于说明,不影响实现。",
|
|
1073
|
-
"- 不可测试时:如函数依赖外部环境或调用未实现符号,暂不生成测试,但请在函数文档注释中注明不可测试原因(例如外部依赖/未实现符号)。",
|
|
1074
|
-
"- 指针+长度参数:如 C 存在 <ptr, len> 组合,请优先保持成对出现;若暂不清晰,至少保留长度参数占位",
|
|
1075
|
-
"- 风格:遵循 rustfmt 默认风格,避免引入与本次改动无关的大范围格式变化;",
|
|
1076
|
-
"- 输出限制:仅以补丁形式修改目标文件,不要输出解释或多余文本。",
|
|
1074
|
+
"【工具】",
|
|
1075
|
+
f"- read_symbols: {{\"symbols_file\": \"{symbols_path}\", \"symbols\": [...]}}",
|
|
1076
|
+
"- read_code: 读取 C 源码或 Rust 模块",
|
|
1077
1077
|
"",
|
|
1078
|
-
"
|
|
1078
|
+
*([f"未转换符号:{', '.join(unresolved)}"] if unresolved else []),
|
|
1079
|
+
"",
|
|
1080
|
+
"C 源码:",
|
|
1079
1081
|
"<C_SOURCE>",
|
|
1080
1082
|
c_code,
|
|
1081
1083
|
"</C_SOURCE>",
|
|
1082
1084
|
"",
|
|
1083
|
-
"
|
|
1085
|
+
"签名参考:",
|
|
1084
1086
|
json.dumps({"signature": getattr(rec, "signature", ""), "params": getattr(rec, "params", None)}, ensure_ascii=False, indent=2),
|
|
1085
1087
|
"",
|
|
1086
|
-
"
|
|
1087
|
-
"",
|
|
1088
|
-
"如对实现细节不确定:可以使用工具 read_symbols 按需获取指定符号记录:",
|
|
1089
|
-
"- 工具: read_symbols",
|
|
1090
|
-
"- 参数示例(YAML):",
|
|
1091
|
-
f" symbols_file: \"{symbols_path}\"",
|
|
1092
|
-
" symbols:",
|
|
1093
|
-
" - 要读取的符号列表",
|
|
1094
|
-
"",
|
|
1095
|
-
"尚未转换的被调符号如下(请阅读这些符号的 C 源码并生成等价的 Rust 实现;必要时新增模块或签名):",
|
|
1096
|
-
*[f"- {s}" for s in (unresolved or [])],
|
|
1097
|
-
"",
|
|
1098
|
-
"【重要:依赖检查与实现要求】",
|
|
1099
|
-
"在实现函数之前,请务必检查以下内容:",
|
|
1100
|
-
"1. 检查当前函数是否已实现:",
|
|
1101
|
-
f" - 在目标模块 {module} 中查找函数 {rec.qname or rec.name} 的实现",
|
|
1102
|
-
" - 如果已存在实现,检查其是否完整且正确",
|
|
1103
|
-
"2. 检查所有依赖函数是否已实现:",
|
|
1104
|
-
" - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
|
|
1105
|
-
" - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
|
|
1106
|
-
" - 可以使用 read_code 工具读取相关模块文件进行检查",
|
|
1107
|
-
" - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
|
|
1108
|
-
"3. 对于未实现的依赖函数:",
|
|
1109
|
-
" - 使用 read_symbols 工具获取其 C 源码和符号信息",
|
|
1110
|
-
" - 使用 read_code 工具读取其 C 源码实现",
|
|
1111
|
-
" - 在本次实现中一并补齐这些依赖函数的 Rust 实现",
|
|
1112
|
-
" - 根据依赖关系选择合适的模块位置(可在同一模块或合理的新模块中)",
|
|
1113
|
-
" - 确保所有依赖函数都有完整实现,禁止使用 todo!/unimplemented! 占位",
|
|
1114
|
-
"4. 实现顺序:",
|
|
1115
|
-
" - 优先实现最底层的依赖函数(不依赖其他未实现函数的函数)",
|
|
1116
|
-
" - 然后实现依赖这些底层函数的函数",
|
|
1117
|
-
" - 最后实现当前目标函数",
|
|
1118
|
-
"5. 验证:",
|
|
1119
|
-
" - 确保当前函数及其所有依赖函数都已完整实现",
|
|
1120
|
-
" - 确保没有遗留的 todo!/unimplemented! 占位",
|
|
1121
|
-
" - 确保所有函数调用都能正确解析",
|
|
1088
|
+
"仅输出补丁,不要解释。",
|
|
1122
1089
|
]
|
|
1123
1090
|
# 若存在库替代上下文,则附加到实现提示中,便于生成器参考(多库组合、参考API、备注等)
|
|
1124
1091
|
librep_ctx = None
|
|
@@ -1133,24 +1100,26 @@ class Transpiler:
|
|
|
1133
1100
|
json.dumps(librep_ctx, ensure_ascii=False, indent=2),
|
|
1134
1101
|
"",
|
|
1135
1102
|
])
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
"- 关键实现细节与设计决策",
|
|
1146
|
-
"- 依赖关系与调用链",
|
|
1147
|
-
"- 类型转换与边界处理要点",
|
|
1148
|
-
"- 错误处理策略",
|
|
1149
|
-
"- 实际实现的 Rust 代码要点与关键逻辑",
|
|
1150
|
-
"记忆标签建议:使用 'c2rust', 'function_impl', 函数名等作为标签,便于后续检索。",
|
|
1151
|
-
"请在完成代码实现之后保存记忆,记录本次实现的完整信息。",
|
|
1152
|
-
])
|
|
1103
|
+
# 添加编译参数(如果存在)
|
|
1104
|
+
compile_flags = self._extract_compile_flags(rec.file)
|
|
1105
|
+
if compile_flags:
|
|
1106
|
+
requirement_lines.extend([
|
|
1107
|
+
"",
|
|
1108
|
+
"C文件编译参数(来自 compile_commands.json):",
|
|
1109
|
+
compile_flags,
|
|
1110
|
+
"",
|
|
1111
|
+
])
|
|
1153
1112
|
prompt = "\n".join(requirement_lines)
|
|
1113
|
+
return self._append_additional_notes(prompt)
|
|
1114
|
+
|
|
1115
|
+
def _codeagent_generate_impl(self, rec: FnRecord, c_code: str, module: str, rust_sig: str, unresolved: List[str]) -> None:
|
|
1116
|
+
"""
|
|
1117
|
+
使用 CodeAgent 生成/更新目标模块中的函数实现。
|
|
1118
|
+
约束:最小变更,生成可编译的占位实现,尽可能保留后续细化空间。
|
|
1119
|
+
"""
|
|
1120
|
+
# 构建提示词
|
|
1121
|
+
prompt = self._build_generate_impl_prompt(rec, c_code, module, rust_sig, unresolved)
|
|
1122
|
+
|
|
1154
1123
|
# 确保目标模块文件存在(提高补丁应用与实现落盘的确定性)
|
|
1155
1124
|
try:
|
|
1156
1125
|
mp = Path(module)
|
|
@@ -1165,24 +1134,52 @@ class Transpiler:
|
|
|
1165
1134
|
pass
|
|
1166
1135
|
except Exception:
|
|
1167
1136
|
pass
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1137
|
+
|
|
1138
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
1139
|
+
# 记录运行前的 commit
|
|
1140
|
+
before_commit = self._get_crate_commit_hash()
|
|
1141
|
+
agent = self._get_code_agent()
|
|
1142
|
+
agent.run(self._compose_prompt_with_context(prompt), prefix="[c2rust-transpiler][gen]", suffix="")
|
|
1143
|
+
|
|
1144
|
+
# 检测并处理测试代码删除
|
|
1145
|
+
if self._check_and_handle_test_deletion(before_commit, agent):
|
|
1146
|
+
# 如果回退了,需要重新运行 agent
|
|
1147
|
+
typer.secho("[c2rust-transpiler][gen] 检测到测试代码删除问题,已回退,重新运行 agent", fg=typer.colors.YELLOW)
|
|
1148
|
+
before_commit = self._get_crate_commit_hash()
|
|
1149
|
+
agent.run(self._compose_prompt_with_context(prompt), prefix="[c2rust-transpiler][gen][retry]", suffix="")
|
|
1150
|
+
# 再次检测
|
|
1151
|
+
if self._check_and_handle_test_deletion(before_commit, agent):
|
|
1152
|
+
typer.secho("[c2rust-transpiler][gen] 再次检测到测试代码删除问题,已回退", fg=typer.colors.RED)
|
|
1153
|
+
|
|
1154
|
+
# 如果是根符号,确保其模块在 lib.rs 中被暴露
|
|
1155
|
+
if self._is_root_symbol(rec):
|
|
1156
|
+
try:
|
|
1157
|
+
mp = Path(module)
|
|
1158
|
+
crate_root = self.crate_dir.resolve()
|
|
1159
|
+
rel = mp.resolve().relative_to(crate_root) if mp.is_absolute() else Path(module)
|
|
1160
|
+
rel_s = str(rel).replace("\\", "/")
|
|
1161
|
+
if rel_s.startswith("./"):
|
|
1162
|
+
rel_s = rel_s[2:]
|
|
1163
|
+
if rel_s.startswith("src/"):
|
|
1164
|
+
parts = rel_s[len("src/"):].strip("/").split("/")
|
|
1165
|
+
if parts and parts[0]:
|
|
1166
|
+
top_mod = parts[0]
|
|
1167
|
+
# 过滤掉 "mod" 关键字和 .rs 文件
|
|
1168
|
+
if top_mod != "mod" and not top_mod.endswith(".rs"):
|
|
1169
|
+
self._ensure_top_level_pub_mod(top_mod)
|
|
1170
|
+
typer.secho(f"[c2rust-transpiler][gen] 根符号 {rec.qname or rec.name} 的模块 {top_mod} 已在 lib.rs 中暴露", fg=typer.colors.GREEN)
|
|
1171
|
+
except Exception:
|
|
1172
|
+
pass
|
|
1173
|
+
|
|
1174
|
+
def _extract_rust_fn_name_from_sig(self, rust_sig: str) -> str:
|
|
1175
|
+
"""
|
|
1176
|
+
从 rust 签名中提取函数名,支持生命周期参数和泛型参数。
|
|
1177
|
+
例如: 'pub fn foo(a: i32) -> i32 { ... }' -> 'foo'
|
|
1178
|
+
例如: 'pub fn foo<'a>(bzf: &'a mut BzFile) -> Result<&'a [u8], BzError>' -> 'foo'
|
|
1179
|
+
"""
|
|
1180
|
+
# 支持生命周期参数和泛型参数:fn name<'a, T>(...)
|
|
1181
|
+
m = re.search(r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(", rust_sig or "")
|
|
1182
|
+
return m.group(1) if m else ""
|
|
1186
1183
|
|
|
1187
1184
|
def _ensure_top_level_pub_mod(self, mod_name: str) -> None:
|
|
1188
1185
|
"""
|
|
@@ -1193,7 +1190,7 @@ class Transpiler:
|
|
|
1193
1190
|
- 最小改动,不覆盖其他内容
|
|
1194
1191
|
"""
|
|
1195
1192
|
try:
|
|
1196
|
-
if not mod_name or mod_name in ("lib", "main"):
|
|
1193
|
+
if not mod_name or mod_name in ("lib", "main", "mod"):
|
|
1197
1194
|
return
|
|
1198
1195
|
lib_rs = (self.crate_dir / "src" / "lib.rs").resolve()
|
|
1199
1196
|
lib_rs.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -1231,7 +1228,7 @@ class Transpiler:
|
|
|
1231
1228
|
- 最小改动,不覆盖其他内容
|
|
1232
1229
|
"""
|
|
1233
1230
|
try:
|
|
1234
|
-
if not child_mod or child_mod in ("lib", "main"):
|
|
1231
|
+
if not child_mod or child_mod in ("lib", "main", "mod"):
|
|
1235
1232
|
return
|
|
1236
1233
|
mod_rs = (dir_path / "mod.rs").resolve()
|
|
1237
1234
|
mod_rs.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -1288,6 +1285,9 @@ class Transpiler:
|
|
|
1288
1285
|
if parts[-1] in ("lib.rs", "main.rs"):
|
|
1289
1286
|
return
|
|
1290
1287
|
child = parts[-1][:-3] # 去掉 .rs
|
|
1288
|
+
# 过滤掉 "mod" 关键字
|
|
1289
|
+
if child == "mod":
|
|
1290
|
+
return
|
|
1291
1291
|
if len(parts) > 1:
|
|
1292
1292
|
start_dir = crate_root / "src" / "/".join(parts[:-1])
|
|
1293
1293
|
else:
|
|
@@ -1333,6 +1333,10 @@ class Transpiler:
|
|
|
1333
1333
|
break
|
|
1334
1334
|
# 在 parent/mod.rs 确保 pub mod <cur_dir.name>
|
|
1335
1335
|
# 确保 parent 在 crate/src 下
|
|
1336
|
+
# 过滤掉 "mod" 关键字
|
|
1337
|
+
if cur_dir.name == "mod":
|
|
1338
|
+
cur_dir = parent
|
|
1339
|
+
continue
|
|
1336
1340
|
try:
|
|
1337
1341
|
parent_rel = parent.relative_to(crate_root)
|
|
1338
1342
|
if str(parent_rel).replace("\\", "/").startswith("src/"):
|
|
@@ -1418,7 +1422,7 @@ class Transpiler:
|
|
|
1418
1422
|
typer.secho(f"[c2rust-transpiler][todo] 未在 src/ 中找到 todo!(\"{symbol}\") 或 unimplemented!(\"{symbol}\") 的出现", fg=typer.colors.BLUE)
|
|
1419
1423
|
return
|
|
1420
1424
|
|
|
1421
|
-
#
|
|
1425
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
1422
1426
|
typer.secho(f"[c2rust-transpiler][todo] 发现 {len(matches)} 个包含 todo!(\"{symbol}\") 或 unimplemented!(\"{symbol}\") 的文件", fg=typer.colors.YELLOW)
|
|
1423
1427
|
for target_file in matches:
|
|
1424
1428
|
prompt = "\n".join([
|
|
@@ -1437,13 +1441,20 @@ class Transpiler:
|
|
|
1437
1441
|
f"仅修改 {target_file} 中与上述占位相关的代码,其他位置不要改动。",
|
|
1438
1442
|
"请仅输出补丁,不要输出解释或多余文本。",
|
|
1439
1443
|
])
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1444
|
+
# 记录运行前的 commit
|
|
1445
|
+
before_commit = self._get_crate_commit_hash()
|
|
1446
|
+
agent = self._get_code_agent()
|
|
1447
|
+
agent.run(self._compose_prompt_with_context(prompt), prefix=f"[c2rust-transpiler][todo-fix:{symbol}]", suffix="")
|
|
1448
|
+
|
|
1449
|
+
# 检测并处理测试代码删除
|
|
1450
|
+
if self._check_and_handle_test_deletion(before_commit, agent):
|
|
1451
|
+
# 如果回退了,需要重新运行 agent
|
|
1452
|
+
typer.secho(f"[c2rust-transpiler][todo-fix] 检测到测试代码删除问题,已回退,重新运行 agent (symbol={symbol})", fg=typer.colors.YELLOW)
|
|
1453
|
+
before_commit = self._get_crate_commit_hash()
|
|
1454
|
+
agent.run(self._compose_prompt_with_context(prompt), prefix=f"[c2rust-transpiler][todo-fix:{symbol}][retry]", suffix="")
|
|
1455
|
+
# 再次检测
|
|
1456
|
+
if self._check_and_handle_test_deletion(before_commit, agent):
|
|
1457
|
+
typer.secho(f"[c2rust-transpiler][todo-fix] 再次检测到测试代码删除问题,已回退 (symbol={symbol})", fg=typer.colors.RED)
|
|
1447
1458
|
|
|
1448
1459
|
def _classify_rust_error(self, text: str) -> List[str]:
|
|
1449
1460
|
"""
|
|
@@ -1507,21 +1518,17 @@ class Transpiler:
|
|
|
1507
1518
|
c_code = ""
|
|
1508
1519
|
return curr, sym_name, src_loc, c_code
|
|
1509
1520
|
|
|
1510
|
-
def
|
|
1521
|
+
def _build_repair_prompt_base(
|
|
1522
|
+
self, stage: str, tags: List[str], sym_name: str, src_loc: str, c_code: str,
|
|
1523
|
+
curr: Dict[str, Any], symbols_path: str, include_output_patch_hint: bool = False
|
|
1524
|
+
) -> List[str]:
|
|
1511
1525
|
"""
|
|
1512
|
-
|
|
1526
|
+
构建修复提示词的基础部分。
|
|
1513
1527
|
|
|
1514
|
-
|
|
1515
|
-
stage: 阶段名称("cargo check" 或 "cargo test")
|
|
1516
|
-
output: 构建错误输出
|
|
1517
|
-
tags: 错误分类标签
|
|
1518
|
-
sym_name: 符号名称
|
|
1519
|
-
src_loc: 源文件位置
|
|
1520
|
-
c_code: C 源码片段
|
|
1521
|
-
curr: 当前进度信息
|
|
1522
|
-
symbols_path: 符号表文件路径
|
|
1523
|
-
include_output_patch_hint: 是否包含"仅输出补丁"提示(test阶段需要)
|
|
1528
|
+
返回基础行列表。
|
|
1524
1529
|
"""
|
|
1530
|
+
# 检查是否为根符号
|
|
1531
|
+
is_root = sym_name in (self.root_symbols or [])
|
|
1525
1532
|
base_lines = [
|
|
1526
1533
|
f"目标:以最小的改动修复问题,使 `{stage}` 命令可以通过。",
|
|
1527
1534
|
f"阶段:{stage}",
|
|
@@ -1531,7 +1538,25 @@ class Transpiler:
|
|
|
1531
1538
|
"- 如构建失败源于缺失或未实现的被调函数/依赖,请阅读其 C 源码并在本次一并补齐等价的 Rust 实现;必要时可在合理的模块中新建函数;",
|
|
1532
1539
|
"- 禁止使用 todo!/unimplemented! 作为占位;",
|
|
1533
1540
|
"- 可使用工具 read_symbols/read_code 获取依赖符号的 C 源码与位置以辅助实现;仅精确导入所需符号,避免通配;",
|
|
1541
|
+
"- **🔍 调试辅助:如果遇到难以定位的问题,可以使用以下方法辅助调试**:",
|
|
1542
|
+
" * 添加临时调试输出:使用 `println!()` 或 `dbg!()` 宏输出关键变量的值、函数调用路径、中间状态等",
|
|
1543
|
+
" * 检查函数调用链:使用 `read_code` 工具读取相关函数的实现,确认调用关系是否正确",
|
|
1544
|
+
" * 验证数据流:在关键位置添加调试输出,检查数据在函数间的传递是否正确",
|
|
1545
|
+
" * 对比 C 实现:使用 `read_symbols` 和 `read_code` 工具读取 C 源码,对比 Rust 实现与 C 实现的差异",
|
|
1546
|
+
" * 检查类型转换:确认 Rust 类型与 C 类型的对应关系是否正确,特别是指针、数组、结构体等",
|
|
1547
|
+
" * 验证边界条件:检查数组边界、空值处理、溢出处理等边界情况",
|
|
1548
|
+
" * 运行单个测试:如果测试套件很大,可以使用 `cargo test -- --nocapture <test_name>` 运行特定测试,加快调试速度",
|
|
1549
|
+
" * 查看完整错误信息:确保阅读完整的错误输出,包括堆栈跟踪、类型信息、位置信息等",
|
|
1550
|
+
" * 注意:调试输出可以在修复后移除,但建议保留关键位置的调试信息直到问题完全解决",
|
|
1551
|
+
"- **⚠️ 重要:修复后必须验证** - 修复完成后,必须使用 `execute_script` 工具执行验证命令:",
|
|
1552
|
+
" * 执行 `cargo test -- --nocapture` 验证编译和测试是否通过",
|
|
1553
|
+
" * 命令必须成功(返回码为 0),才说明修复成功",
|
|
1554
|
+
" * **不要假设修复成功,必须实际执行命令验证**",
|
|
1555
|
+
" * **cargo test 会自动编译,无需单独执行 cargo check**",
|
|
1556
|
+
"- 注释规范:所有代码注释(包括文档注释、行内注释、块注释等)必须使用中文;",
|
|
1534
1557
|
f"- 依赖管理:如修复中引入新的外部 crate 或需要启用 feature,请同步更新 Cargo.toml 的 [dependencies]/[dev-dependencies]/[features]{(',避免未声明依赖导致构建失败;版本号可使用兼容范围(如 ^x.y)或默认值' if stage == 'cargo test' else '')};",
|
|
1558
|
+
*([f"- **禁用库约束**:禁止在修复中使用以下库:{', '.join(self.disabled_libraries)}。如果这些库在 Cargo.toml 中已存在,请移除相关依赖;如果修复需要使用这些库的功能,请使用标准库或其他允许的库替代。"] if self.disabled_libraries else []),
|
|
1559
|
+
*([f"- **根符号要求**:此函数是根符号({sym_name}),必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。"] if is_root else []),
|
|
1535
1560
|
"",
|
|
1536
1561
|
"【重要:依赖检查与实现要求】",
|
|
1537
1562
|
"在修复问题之前,请务必检查以下内容:",
|
|
@@ -1543,7 +1568,6 @@ class Transpiler:
|
|
|
1543
1568
|
" - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
|
|
1544
1569
|
" - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
|
|
1545
1570
|
" - 可以使用 read_code 工具读取相关模块文件进行检查",
|
|
1546
|
-
" - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
|
|
1547
1571
|
"3. 对于未实现的依赖函数:",
|
|
1548
1572
|
" - 使用 read_symbols 工具获取其 C 源码和符号信息",
|
|
1549
1573
|
" - 使用 read_code 工具读取其 C 源码实现",
|
|
@@ -1573,217 +1597,486 @@ class Transpiler:
|
|
|
1573
1597
|
"<C_SOURCE>",
|
|
1574
1598
|
c_code,
|
|
1575
1599
|
"</C_SOURCE>",
|
|
1600
|
+
])
|
|
1601
|
+
# 添加编译参数(如果存在)
|
|
1602
|
+
c_file_path = curr.get("file") or ""
|
|
1603
|
+
if c_file_path:
|
|
1604
|
+
compile_flags = self._extract_compile_flags(c_file_path)
|
|
1605
|
+
if compile_flags:
|
|
1606
|
+
base_lines.extend([
|
|
1607
|
+
"",
|
|
1608
|
+
"C文件编译参数(来自 compile_commands.json):",
|
|
1609
|
+
compile_flags,
|
|
1610
|
+
])
|
|
1611
|
+
base_lines.extend([
|
|
1576
1612
|
"",
|
|
1577
|
-
"
|
|
1578
|
-
"
|
|
1579
|
-
"-
|
|
1580
|
-
|
|
1581
|
-
"
|
|
1582
|
-
f"
|
|
1613
|
+
"【工具使用建议】",
|
|
1614
|
+
"1. 符号表检索:",
|
|
1615
|
+
" - 工具: read_symbols",
|
|
1616
|
+
" - 用途: 定位或交叉验证 C 符号位置",
|
|
1617
|
+
" - 参数示例(JSON):",
|
|
1618
|
+
f" {{\"symbols_file\": \"{symbols_path}\", \"symbols\": [\"{sym_name}\"]}}",
|
|
1619
|
+
"",
|
|
1620
|
+
"2. 代码读取:",
|
|
1621
|
+
" - 工具: read_code",
|
|
1622
|
+
" - 用途: 读取 C 源码实现或 Rust 模块文件",
|
|
1623
|
+
" - 调试用途: 当遇到问题时,可以读取相关文件检查实现是否正确",
|
|
1624
|
+
"",
|
|
1625
|
+
"3. 脚本执行(调试辅助):",
|
|
1626
|
+
" - 工具: execute_script",
|
|
1627
|
+
" - 调试用途:",
|
|
1628
|
+
" * 执行 `cargo test -- --nocapture <test_name>` 运行特定测试,加快调试速度",
|
|
1629
|
+
" * 执行 `cargo test --message-format=short --no-run` 只检查编译,不运行测试",
|
|
1630
|
+
" * 执行 `cargo check` 快速检查编译错误(如果测试太慢)",
|
|
1631
|
+
" * 执行 `cargo test --lib` 只运行库测试,跳过集成测试",
|
|
1632
|
+
" * 执行 `cargo test --test <test_file>` 运行特定的测试文件",
|
|
1583
1633
|
"",
|
|
1584
1634
|
"上下文:",
|
|
1585
1635
|
f"- crate 根目录路径: {self.crate_dir.resolve()}",
|
|
1636
|
+
f"- 包名称(用于 cargo -p): {self.crate_dir.name}",
|
|
1586
1637
|
])
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1638
|
+
return base_lines
|
|
1639
|
+
|
|
1640
|
+
def _build_repair_prompt_stage_section(
|
|
1641
|
+
self, stage: str, output: str, command: Optional[str] = None
|
|
1642
|
+
) -> List[str]:
|
|
1643
|
+
"""
|
|
1644
|
+
构建修复提示词的阶段特定部分(测试或检查)。
|
|
1645
|
+
|
|
1646
|
+
返回阶段特定的行列表。
|
|
1647
|
+
"""
|
|
1648
|
+
section_lines: List[str] = []
|
|
1591
1649
|
if stage == "cargo test":
|
|
1592
|
-
|
|
1650
|
+
section_lines.extend([
|
|
1593
1651
|
"",
|
|
1594
|
-
"
|
|
1652
|
+
"【⚠️ 重要:测试失败 - 必须修复】",
|
|
1595
1653
|
"以下输出来自 `cargo test` 命令,包含测试执行结果和失败详情:",
|
|
1654
|
+
"- **测试当前状态:失败** - 必须修复才能继续",
|
|
1596
1655
|
"- 如果看到测试用例名称和断言失败,说明测试逻辑或实现有问题",
|
|
1597
1656
|
"- 如果看到编译错误,说明代码存在语法或类型错误",
|
|
1598
|
-
"-
|
|
1657
|
+
"- **请仔细阅读失败信息**,包括:",
|
|
1658
|
+
" * 测试用例名称(如 `test_bz_read_get_unused`)",
|
|
1659
|
+
" * 失败位置(文件路径和行号,如 `src/ffi/decompress.rs:76:47`)",
|
|
1660
|
+
" * 错误类型(如 `SequenceError`、`Result::unwrap()` 失败等)",
|
|
1661
|
+
" * 期望值与实际值的差异",
|
|
1662
|
+
" * 完整的堆栈跟踪信息",
|
|
1663
|
+
"",
|
|
1664
|
+
"**关键要求:**",
|
|
1665
|
+
"- 必须分析测试失败的根本原因,而不是假设问题已解决",
|
|
1666
|
+
"- 必须实际修复导致测试失败的代码,而不是只修改测试用例",
|
|
1667
|
+
"- 修复后必须确保测试能够通过,而不是只修复编译错误",
|
|
1599
1668
|
"",
|
|
1600
|
-
|
|
1669
|
+
])
|
|
1670
|
+
if command:
|
|
1671
|
+
section_lines.append(f"执行的命令:{command}")
|
|
1672
|
+
section_lines.append("提示:如果不相信上述命令执行结果,可以使用 execute_script 工具自己执行一次该命令进行验证。")
|
|
1673
|
+
section_lines.extend([
|
|
1674
|
+
"",
|
|
1675
|
+
"【测试失败详细信息 - 必须仔细阅读并修复】",
|
|
1676
|
+
"以下是从 `cargo test` 命令获取的完整输出,包含测试失败的具体信息:",
|
|
1601
1677
|
"<TEST_FAILURE>",
|
|
1602
1678
|
output,
|
|
1603
1679
|
"</TEST_FAILURE>",
|
|
1604
1680
|
"",
|
|
1681
|
+
"**修复要求:**",
|
|
1682
|
+
"1. 仔细分析上述测试失败信息,找出失败的根本原因",
|
|
1683
|
+
"2. 定位到具体的代码位置(文件路径和行号)",
|
|
1684
|
+
"3. **如果问题难以定位,添加调试信息辅助定位**:",
|
|
1685
|
+
" - 在关键位置添加 `println!()` 或 `dbg!()` 输出变量值、函数调用路径、中间状态",
|
|
1686
|
+
" - 检查函数参数和返回值是否正确传递",
|
|
1687
|
+
" - 验证数据结构和类型转换是否正确",
|
|
1688
|
+
" - 对比 C 实现与 Rust 实现的差异,找出可能导致问题的点",
|
|
1689
|
+
" - 使用 `read_code` 工具读取相关函数的实现,确认逻辑是否正确",
|
|
1690
|
+
" - 如果测试输出信息不足,可以添加更详细的调试输出来定位问题",
|
|
1691
|
+
"4. 修复导致测试失败的代码逻辑",
|
|
1692
|
+
"5. 确保修复后测试能够通过(不要只修复编译错误)",
|
|
1693
|
+
"6. 如果测试用例本身有问题,可以修改测试用例,但必须确保测试能够正确验证函数行为",
|
|
1694
|
+
"",
|
|
1695
|
+
"**⚠️ 重要:修复后必须验证**",
|
|
1696
|
+
"- 修复完成后,**必须使用 `execute_script` 工具执行以下命令验证修复效果**:",
|
|
1697
|
+
f" - 命令:`{command or 'cargo test -- --nocapture'}`",
|
|
1698
|
+
"- 验证要求:",
|
|
1699
|
+
" * 如果命令执行成功(返回码为 0),说明修复成功",
|
|
1700
|
+
" * 如果命令执行失败(返回码非 0),说明修复未成功,需要继续修复",
|
|
1701
|
+
" * **不要假设修复成功,必须实际执行命令验证**",
|
|
1702
|
+
"- 如果验证失败,请分析失败原因并继续修复,直到验证通过",
|
|
1703
|
+
"",
|
|
1605
1704
|
"修复后请再次执行 `cargo test` 进行验证。",
|
|
1606
1705
|
])
|
|
1607
1706
|
else:
|
|
1608
|
-
|
|
1707
|
+
section_lines.extend([
|
|
1609
1708
|
"",
|
|
1610
1709
|
"请阅读以下构建错误并进行必要修复:",
|
|
1710
|
+
])
|
|
1711
|
+
if command:
|
|
1712
|
+
section_lines.append(f"执行的命令:{command}")
|
|
1713
|
+
section_lines.append("提示:如果不相信上述命令执行结果,可以使用 execute_script 工具自己执行一次该命令进行验证。")
|
|
1714
|
+
section_lines.extend([
|
|
1715
|
+
"",
|
|
1611
1716
|
"<BUILD_ERROR>",
|
|
1612
1717
|
output,
|
|
1613
1718
|
"</BUILD_ERROR>",
|
|
1614
1719
|
"",
|
|
1615
|
-
"
|
|
1720
|
+
"**修复要求:**",
|
|
1721
|
+
"1. 仔细分析上述构建错误信息,找出错误的根本原因",
|
|
1722
|
+
"2. 定位到具体的代码位置(文件路径和行号)",
|
|
1723
|
+
"3. **如果问题难以定位,添加调试信息辅助定位**:",
|
|
1724
|
+
" - 使用 `read_code` 工具读取相关文件,检查代码实现是否正确",
|
|
1725
|
+
" - 检查类型定义、函数签名、模块导入等是否正确",
|
|
1726
|
+
" - 验证依赖关系是否正确,所有被调用的函数/类型是否已定义",
|
|
1727
|
+
" - 如果错误信息不够清晰,可以尝试编译单个文件或模块来缩小问题范围",
|
|
1728
|
+
" - 对比 C 实现与 Rust 实现的差异,确认类型映射是否正确",
|
|
1729
|
+
"4. 修复导致构建错误的代码",
|
|
1730
|
+
"5. 确保修复后代码能够编译通过",
|
|
1731
|
+
"",
|
|
1732
|
+
"**⚠️ 重要:修复后必须验证**",
|
|
1733
|
+
"- 修复完成后,**必须使用 `execute_script` 工具执行以下命令验证修复效果**:",
|
|
1734
|
+
" - 命令:`cargo test -- --nocapture`",
|
|
1735
|
+
"- 验证要求:",
|
|
1736
|
+
" * 命令必须执行成功(返回码为 0),才说明修复成功",
|
|
1737
|
+
" * 如果命令执行失败(返回码非 0),说明修复未成功,需要继续修复",
|
|
1738
|
+
" * **不要假设修复成功,必须实际执行命令验证**",
|
|
1739
|
+
"- 如果验证失败,请分析失败原因并继续修复,直到验证通过",
|
|
1740
|
+
"",
|
|
1741
|
+
"修复后请执行 `cargo test -- --nocapture` 进行验证。",
|
|
1616
1742
|
])
|
|
1617
|
-
return
|
|
1743
|
+
return section_lines
|
|
1618
1744
|
|
|
1619
|
-
def
|
|
1620
|
-
"""
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
|
|
1656
|
-
err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
|
|
1657
|
-
cur["last_build_error"] = err_summary
|
|
1658
|
-
self.progress["current"] = cur
|
|
1659
|
-
self._save_progress()
|
|
1660
|
-
except Exception:
|
|
1661
|
-
pass
|
|
1662
|
-
return False
|
|
1663
|
-
# 提示修复(分类标签)
|
|
1664
|
-
tags = self._classify_rust_error(output)
|
|
1665
|
-
symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
|
|
1666
|
-
curr, sym_name, src_loc, c_code = self._get_current_function_context()
|
|
1667
|
-
repair_prompt = self._build_repair_prompt(
|
|
1668
|
-
stage="cargo check",
|
|
1669
|
-
output=output,
|
|
1670
|
-
tags=tags,
|
|
1671
|
-
sym_name=sym_name,
|
|
1672
|
-
src_loc=src_loc,
|
|
1673
|
-
c_code=c_code,
|
|
1674
|
-
curr=curr,
|
|
1675
|
-
symbols_path=symbols_path,
|
|
1676
|
-
include_output_patch_hint=False,
|
|
1677
|
-
)
|
|
1678
|
-
prev_cwd = os.getcwd()
|
|
1745
|
+
def _build_repair_prompt(self, stage: str, output: str, tags: List[str], sym_name: str, src_loc: str, c_code: str, curr: Dict[str, Any], symbols_path: str, include_output_patch_hint: bool = False, command: Optional[str] = None) -> str:
|
|
1746
|
+
"""
|
|
1747
|
+
构建修复提示词。
|
|
1748
|
+
|
|
1749
|
+
Args:
|
|
1750
|
+
stage: 阶段名称("cargo test")
|
|
1751
|
+
output: 构建错误输出
|
|
1752
|
+
tags: 错误分类标签
|
|
1753
|
+
sym_name: 符号名称
|
|
1754
|
+
src_loc: 源文件位置
|
|
1755
|
+
c_code: C 源码片段
|
|
1756
|
+
curr: 当前进度信息
|
|
1757
|
+
symbols_path: 符号表文件路径
|
|
1758
|
+
include_output_patch_hint: 是否包含"仅输出补丁"提示(test阶段需要)
|
|
1759
|
+
command: 执行的命令(可选)
|
|
1760
|
+
"""
|
|
1761
|
+
base_lines = self._build_repair_prompt_base(
|
|
1762
|
+
stage, tags, sym_name, src_loc, c_code, curr, symbols_path, include_output_patch_hint
|
|
1763
|
+
)
|
|
1764
|
+
stage_lines = self._build_repair_prompt_stage_section(stage, output, command)
|
|
1765
|
+
prompt = "\n".join(base_lines + stage_lines)
|
|
1766
|
+
return self._append_additional_notes(prompt)
|
|
1767
|
+
|
|
1768
|
+
def _detect_crate_kind(self) -> str:
|
|
1769
|
+
"""
|
|
1770
|
+
检测 crate 类型:lib、bin 或 mixed。
|
|
1771
|
+
判定规则(尽量保守,避免误判):
|
|
1772
|
+
- 若存在 src/lib.rs 或 Cargo.toml 中包含 [lib],视为包含 lib
|
|
1773
|
+
- 若存在 src/main.rs 或 Cargo.toml 中包含 [[bin]](或 [bin] 兼容),视为包含 bin
|
|
1774
|
+
- 同时存在则返回 mixed
|
|
1775
|
+
- 两者都不明确时,默认返回 lib(与默认模版一致)
|
|
1776
|
+
"""
|
|
1777
|
+
try:
|
|
1778
|
+
cargo_path = (self.crate_dir / "Cargo.toml").resolve()
|
|
1779
|
+
txt = ""
|
|
1780
|
+
if cargo_path.exists():
|
|
1679
1781
|
try:
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
# 下一轮循环
|
|
1698
|
-
continue
|
|
1782
|
+
txt = cargo_path.read_text(encoding="utf-8", errors="ignore")
|
|
1783
|
+
except Exception:
|
|
1784
|
+
txt = ""
|
|
1785
|
+
txt_lower = txt.lower()
|
|
1786
|
+
has_lib = (self.crate_dir / "src" / "lib.rs").exists() or bool(re.search(r"(?m)^\s*\[lib\]\s*$", txt_lower))
|
|
1787
|
+
# 兼容:[[bin]] 为数组表,极少数项目也会写成 [bin]
|
|
1788
|
+
has_bin = (self.crate_dir / "src" / "main.rs").exists() or bool(re.search(r"(?m)^\s*\[\[bin\]\]\s*$", txt_lower) or re.search(r"(?m)^\s*\[bin\]\s*$", txt_lower))
|
|
1789
|
+
if has_lib and has_bin:
|
|
1790
|
+
return "mixed"
|
|
1791
|
+
if has_bin:
|
|
1792
|
+
return "bin"
|
|
1793
|
+
if has_lib:
|
|
1794
|
+
return "lib"
|
|
1795
|
+
except Exception:
|
|
1796
|
+
pass
|
|
1797
|
+
# 默认假设为 lib
|
|
1798
|
+
return "lib"
|
|
1699
1799
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1800
|
+
def _run_cargo_fmt(self, workspace_root: str) -> None:
|
|
1801
|
+
"""执行 cargo fmt 格式化代码"""
|
|
1802
|
+
try:
|
|
1703
1803
|
res = subprocess.run(
|
|
1704
|
-
["cargo", "
|
|
1804
|
+
["cargo", "fmt"],
|
|
1705
1805
|
capture_output=True,
|
|
1706
1806
|
text=True,
|
|
1707
1807
|
check=False,
|
|
1708
1808
|
cwd=workspace_root,
|
|
1709
1809
|
)
|
|
1710
1810
|
if res.returncode == 0:
|
|
1711
|
-
typer.secho("[c2rust-transpiler][
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
cur["impl_verified"] = True
|
|
1719
|
-
cur["failed_stage"] = None
|
|
1720
|
-
self.progress["current"] = cur
|
|
1721
|
-
self._save_progress()
|
|
1722
|
-
except Exception:
|
|
1723
|
-
pass
|
|
1724
|
-
return True
|
|
1811
|
+
typer.secho("[c2rust-transpiler][fmt] 代码格式化完成", fg=typer.colors.CYAN)
|
|
1812
|
+
else:
|
|
1813
|
+
# fmt 失败不影响主流程,只记录警告
|
|
1814
|
+
typer.secho(f"[c2rust-transpiler][fmt] 代码格式化失败(非致命): {res.stderr or res.stdout}", fg=typer.colors.YELLOW)
|
|
1815
|
+
except Exception as e:
|
|
1816
|
+
# fmt 失败不影响主流程,只记录警告
|
|
1817
|
+
typer.secho(f"[c2rust-transpiler][fmt] 代码格式化异常(非致命): {e}", fg=typer.colors.YELLOW)
|
|
1725
1818
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
cur = self.progress.get("current") or {}
|
|
1735
|
-
metrics = cur.get("metrics") or {}
|
|
1736
|
-
metrics["check_attempts"] = int(check_iter)
|
|
1737
|
-
metrics["test_attempts"] = int(test_iter)
|
|
1738
|
-
cur["metrics"] = metrics
|
|
1739
|
-
cur["impl_verified"] = False
|
|
1740
|
-
cur["failed_stage"] = "test"
|
|
1741
|
-
err_summary = (output or "").strip()
|
|
1742
|
-
if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
|
|
1743
|
-
err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
|
|
1744
|
-
cur["last_build_error"] = err_summary
|
|
1745
|
-
self.progress["current"] = cur
|
|
1746
|
-
self._save_progress()
|
|
1747
|
-
except Exception:
|
|
1748
|
-
pass
|
|
1749
|
-
return False
|
|
1819
|
+
def _get_crate_commit_hash(self) -> Optional[str]:
|
|
1820
|
+
"""获取 crate 目录的当前 commit id"""
|
|
1821
|
+
try:
|
|
1822
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
1823
|
+
commit_hash = get_latest_commit_hash()
|
|
1824
|
+
return commit_hash if commit_hash else None
|
|
1825
|
+
except Exception:
|
|
1826
|
+
return None
|
|
1750
1827
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
c_code=c_code,
|
|
1762
|
-
curr=curr,
|
|
1763
|
-
symbols_path=symbols_path,
|
|
1764
|
-
include_output_patch_hint=True,
|
|
1828
|
+
def _reset_to_commit(self, commit_hash: str) -> bool:
|
|
1829
|
+
"""回退 crate 目录到指定的 commit"""
|
|
1830
|
+
try:
|
|
1831
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
1832
|
+
# 检查是否是 git 仓库
|
|
1833
|
+
result = subprocess.run(
|
|
1834
|
+
["git", "rev-parse", "--git-dir"],
|
|
1835
|
+
capture_output=True,
|
|
1836
|
+
text=True,
|
|
1837
|
+
check=False,
|
|
1765
1838
|
)
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1839
|
+
if result.returncode != 0:
|
|
1840
|
+
# 不是 git 仓库,无法回退
|
|
1841
|
+
return False
|
|
1842
|
+
|
|
1843
|
+
# 执行硬重置
|
|
1844
|
+
result = subprocess.run(
|
|
1845
|
+
["git", "reset", "--hard", commit_hash],
|
|
1846
|
+
capture_output=True,
|
|
1847
|
+
text=True,
|
|
1848
|
+
check=False,
|
|
1849
|
+
)
|
|
1850
|
+
if result.returncode == 0:
|
|
1851
|
+
# 清理未跟踪的文件
|
|
1852
|
+
subprocess.run(
|
|
1853
|
+
["git", "clean", "-fd"],
|
|
1774
1854
|
capture_output=True,
|
|
1775
1855
|
text=True,
|
|
1776
1856
|
check=False,
|
|
1777
|
-
cwd=workspace_root,
|
|
1778
1857
|
)
|
|
1779
|
-
|
|
1780
|
-
|
|
1858
|
+
return True
|
|
1859
|
+
return False
|
|
1860
|
+
except Exception:
|
|
1861
|
+
return False
|
|
1862
|
+
|
|
1863
|
+
def _check_and_handle_test_deletion(self, before_commit: Optional[str], agent: Any) -> bool:
|
|
1864
|
+
"""
|
|
1865
|
+
检测并处理测试代码删除。
|
|
1866
|
+
|
|
1867
|
+
参数:
|
|
1868
|
+
before_commit: agent 运行前的 commit hash
|
|
1869
|
+
agent: 代码生成或修复的 agent 实例,使用其 model 进行询问
|
|
1870
|
+
|
|
1871
|
+
返回:
|
|
1872
|
+
bool: 如果检测到问题且已回退,返回 True;否则返回 False
|
|
1873
|
+
"""
|
|
1874
|
+
return check_and_handle_test_deletion(
|
|
1875
|
+
before_commit,
|
|
1876
|
+
agent,
|
|
1877
|
+
self._reset_to_commit,
|
|
1878
|
+
"[c2rust-transpiler]"
|
|
1879
|
+
)
|
|
1880
|
+
|
|
1881
|
+
def _run_cargo_test_and_fix(self, workspace_root: str, test_iter: int) -> Tuple[bool, Optional[bool]]:
|
|
1882
|
+
"""
|
|
1883
|
+
运行 cargo test 并在失败时修复。
|
|
1884
|
+
|
|
1885
|
+
Returns:
|
|
1886
|
+
(是否成功, 是否需要回退重新开始,None表示需要回退)
|
|
1887
|
+
"""
|
|
1888
|
+
# 测试失败时需要详细输出,移除 -q 参数以获取完整的测试失败信息(包括堆栈跟踪、断言详情等)
|
|
1889
|
+
try:
|
|
1890
|
+
res_test = subprocess.run(
|
|
1891
|
+
["cargo", "test", "--", "--nocapture"],
|
|
1892
|
+
capture_output=True,
|
|
1893
|
+
text=True,
|
|
1894
|
+
timeout=30,
|
|
1895
|
+
check=False,
|
|
1896
|
+
cwd=workspace_root,
|
|
1897
|
+
)
|
|
1898
|
+
returncode = res_test.returncode
|
|
1899
|
+
stdout = res_test.stdout or ""
|
|
1900
|
+
stderr = res_test.stderr or ""
|
|
1901
|
+
except subprocess.TimeoutExpired as e:
|
|
1902
|
+
# 超时视为测试失败,继续修复流程
|
|
1903
|
+
returncode = -1
|
|
1904
|
+
stdout = e.stdout.decode("utf-8", errors="replace") if e.stdout else ""
|
|
1905
|
+
stderr = "命令执行超时(30秒)\n" + (e.stderr.decode("utf-8", errors="replace") if e.stderr else "")
|
|
1906
|
+
typer.secho("[c2rust-transpiler][build] Cargo 测试超时(30秒),视为失败并继续修复流程", fg=typer.colors.YELLOW)
|
|
1907
|
+
except Exception as e:
|
|
1908
|
+
# 其他异常也视为测试失败
|
|
1909
|
+
returncode = -1
|
|
1910
|
+
stdout = ""
|
|
1911
|
+
stderr = f"执行 cargo test 时发生异常: {str(e)}"
|
|
1912
|
+
typer.secho(f"[c2rust-transpiler][build] Cargo 测试执行异常: {e},视为失败并继续修复流程", fg=typer.colors.YELLOW)
|
|
1913
|
+
|
|
1914
|
+
if returncode == 0:
|
|
1915
|
+
typer.secho("[c2rust-transpiler][build] Cargo 测试通过。", fg=typer.colors.GREEN)
|
|
1916
|
+
# 测试通过,重置连续失败计数
|
|
1917
|
+
self._consecutive_fix_failures = 0
|
|
1918
|
+
try:
|
|
1919
|
+
cur = self.progress.get("current") or {}
|
|
1920
|
+
metrics = cur.get("metrics") or {}
|
|
1921
|
+
metrics["test_attempts"] = int(test_iter)
|
|
1922
|
+
cur["metrics"] = metrics
|
|
1923
|
+
cur["impl_verified"] = True
|
|
1924
|
+
cur["failed_stage"] = None
|
|
1925
|
+
self.progress["current"] = cur
|
|
1926
|
+
self._save_progress()
|
|
1927
|
+
except Exception:
|
|
1928
|
+
pass
|
|
1929
|
+
return (True, False)
|
|
1930
|
+
|
|
1931
|
+
# 测试失败
|
|
1932
|
+
output = stdout + "\n" + stderr
|
|
1933
|
+
limit_info = f" (上限: {self.test_max_retries if self.test_max_retries > 0 else '无限'})" if test_iter % 10 == 0 or test_iter == 1 else ""
|
|
1934
|
+
typer.secho(f"[c2rust-transpiler][build] Cargo 测试失败 (第 {test_iter} 次尝试{limit_info})。", fg=typer.colors.RED)
|
|
1935
|
+
typer.secho(output, fg=typer.colors.RED)
|
|
1936
|
+
maxr = self.test_max_retries
|
|
1937
|
+
if maxr > 0 and test_iter >= maxr:
|
|
1938
|
+
typer.secho(f"[c2rust-transpiler][build] 已达到最大重试次数上限({maxr}),停止构建修复循环。", fg=typer.colors.RED)
|
|
1939
|
+
try:
|
|
1940
|
+
cur = self.progress.get("current") or {}
|
|
1941
|
+
metrics = cur.get("metrics") or {}
|
|
1942
|
+
metrics["test_attempts"] = int(test_iter)
|
|
1943
|
+
cur["metrics"] = metrics
|
|
1944
|
+
cur["impl_verified"] = False
|
|
1945
|
+
cur["failed_stage"] = "test"
|
|
1946
|
+
err_summary = (output or "").strip()
|
|
1947
|
+
if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
|
|
1948
|
+
err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
|
|
1949
|
+
cur["last_build_error"] = err_summary
|
|
1950
|
+
self.progress["current"] = cur
|
|
1951
|
+
self._save_progress()
|
|
1952
|
+
except Exception:
|
|
1953
|
+
pass
|
|
1954
|
+
return (False, False)
|
|
1955
|
+
|
|
1956
|
+
# 构建失败(测试阶段)修复
|
|
1957
|
+
tags = self._classify_rust_error(output)
|
|
1958
|
+
symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
|
|
1959
|
+
curr, sym_name, src_loc, c_code = self._get_current_function_context()
|
|
1960
|
+
|
|
1961
|
+
# 调试输出:确认测试失败信息是否正确传递
|
|
1962
|
+
typer.secho(f"[c2rust-transpiler][debug] 测试失败信息长度: {len(output)} 字符", fg=typer.colors.CYAN)
|
|
1963
|
+
if output:
|
|
1964
|
+
# 提取关键错误信息用于调试
|
|
1965
|
+
error_lines = output.split('\n')
|
|
1966
|
+
key_errors = [line for line in error_lines if any(keyword in line.lower() for keyword in ['failed', 'error', 'panic', 'unwrap', 'sequence'])]
|
|
1967
|
+
if key_errors:
|
|
1968
|
+
typer.secho("[c2rust-transpiler][debug] 关键错误信息(前5行):", fg=typer.colors.CYAN)
|
|
1969
|
+
for i, line in enumerate(key_errors[:5], 1):
|
|
1970
|
+
typer.secho(f" {i}. {line[:100]}", fg=typer.colors.CYAN)
|
|
1971
|
+
|
|
1972
|
+
repair_prompt = self._build_repair_prompt(
|
|
1973
|
+
stage="cargo test",
|
|
1974
|
+
output=output,
|
|
1975
|
+
tags=tags,
|
|
1976
|
+
sym_name=sym_name,
|
|
1977
|
+
src_loc=src_loc,
|
|
1978
|
+
c_code=c_code,
|
|
1979
|
+
curr=curr,
|
|
1980
|
+
symbols_path=symbols_path,
|
|
1981
|
+
include_output_patch_hint=True,
|
|
1982
|
+
command="cargo test -- --nocapture",
|
|
1983
|
+
)
|
|
1984
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
1985
|
+
# 记录运行前的 commit
|
|
1986
|
+
before_commit = self._get_crate_commit_hash()
|
|
1987
|
+
agent = self._get_code_agent()
|
|
1988
|
+
agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={test_iter}][test]", suffix="")
|
|
1989
|
+
|
|
1990
|
+
# 检测并处理测试代码删除
|
|
1991
|
+
if self._check_and_handle_test_deletion(before_commit, agent):
|
|
1992
|
+
# 如果回退了,需要重新运行 agent
|
|
1993
|
+
typer.secho(f"[c2rust-transpiler][build-fix] 检测到测试代码删除问题,已回退,重新运行 agent (iter={test_iter})", fg=typer.colors.YELLOW)
|
|
1994
|
+
before_commit = self._get_crate_commit_hash()
|
|
1995
|
+
agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={test_iter}][test][retry]", suffix="")
|
|
1996
|
+
# 再次检测
|
|
1997
|
+
if self._check_and_handle_test_deletion(before_commit, agent):
|
|
1998
|
+
typer.secho(f"[c2rust-transpiler][build-fix] 再次检测到测试代码删除问题,已回退 (iter={test_iter})", fg=typer.colors.RED)
|
|
1999
|
+
|
|
2000
|
+
# 修复后验证:先检查编译,再实际运行测试
|
|
2001
|
+
# 第一步:检查编译是否通过
|
|
2002
|
+
res_compile = subprocess.run(
|
|
2003
|
+
["cargo", "test", "--message-format=short", "-q", "--no-run"],
|
|
2004
|
+
capture_output=True,
|
|
2005
|
+
text=True,
|
|
2006
|
+
check=False,
|
|
2007
|
+
cwd=workspace_root,
|
|
2008
|
+
)
|
|
2009
|
+
if res_compile.returncode != 0:
|
|
2010
|
+
typer.secho("[c2rust-transpiler][build] 修复后编译仍有错误,将在下一轮循环中处理", fg=typer.colors.YELLOW)
|
|
2011
|
+
# 编译失败,增加连续失败计数
|
|
2012
|
+
self._consecutive_fix_failures += 1
|
|
2013
|
+
# 检查是否需要回退
|
|
2014
|
+
if self._consecutive_fix_failures >= CONSECUTIVE_FIX_FAILURE_THRESHOLD and self._current_function_start_commit:
|
|
2015
|
+
typer.secho(f"[c2rust-transpiler][build] 连续修复失败 {self._consecutive_fix_failures} 次,回退到函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.RED)
|
|
2016
|
+
if self._reset_to_commit(self._current_function_start_commit):
|
|
2017
|
+
typer.secho("[c2rust-transpiler][build] 已回退到函数开始时的 commit,将重新开始处理该函数", fg=typer.colors.YELLOW)
|
|
2018
|
+
# 返回特殊值,表示需要重新开始
|
|
2019
|
+
return (False, None) # type: ignore
|
|
1781
2020
|
else:
|
|
1782
|
-
typer.secho("[c2rust-transpiler][build]
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2021
|
+
typer.secho("[c2rust-transpiler][build] 回退失败,继续尝试修复", fg=typer.colors.YELLOW)
|
|
2022
|
+
return (False, False) # 需要继续循环
|
|
2023
|
+
|
|
2024
|
+
# 第二步:编译通过,实际运行测试验证
|
|
2025
|
+
try:
|
|
2026
|
+
res_test_verify = subprocess.run(
|
|
2027
|
+
["cargo", "test", "--", "--nocapture"],
|
|
2028
|
+
capture_output=True,
|
|
2029
|
+
text=True,
|
|
2030
|
+
timeout=30,
|
|
2031
|
+
check=False,
|
|
2032
|
+
cwd=workspace_root,
|
|
2033
|
+
)
|
|
2034
|
+
verify_returncode = res_test_verify.returncode
|
|
2035
|
+
except subprocess.TimeoutExpired:
|
|
2036
|
+
# 超时视为测试失败
|
|
2037
|
+
verify_returncode = -1
|
|
2038
|
+
typer.secho("[c2rust-transpiler][build] 修复后验证测试超时(30秒),视为失败", fg=typer.colors.YELLOW)
|
|
2039
|
+
except Exception as e:
|
|
2040
|
+
# 其他异常也视为测试失败
|
|
2041
|
+
verify_returncode = -1
|
|
2042
|
+
typer.secho(f"[c2rust-transpiler][build] 修复后验证测试执行异常: {e},视为失败", fg=typer.colors.YELLOW)
|
|
2043
|
+
|
|
2044
|
+
if verify_returncode == 0:
|
|
2045
|
+
typer.secho("[c2rust-transpiler][build] 修复后测试通过,继续构建循环", fg=typer.colors.GREEN)
|
|
2046
|
+
# 测试真正通过,重置连续失败计数
|
|
2047
|
+
self._consecutive_fix_failures = 0
|
|
2048
|
+
return (False, False) # 需要继续循环(但下次应该会通过)
|
|
2049
|
+
else:
|
|
2050
|
+
# 编译通过但测试仍然失败,说明修复没有解决测试逻辑问题
|
|
2051
|
+
typer.secho("[c2rust-transpiler][build] 修复后编译通过,但测试仍然失败,将在下一轮循环中处理", fg=typer.colors.YELLOW)
|
|
2052
|
+
# 测试失败,增加连续失败计数(即使编译通过)
|
|
2053
|
+
self._consecutive_fix_failures += 1
|
|
2054
|
+
# 检查是否需要回退
|
|
2055
|
+
if self._consecutive_fix_failures >= CONSECUTIVE_FIX_FAILURE_THRESHOLD and self._current_function_start_commit:
|
|
2056
|
+
typer.secho(f"[c2rust-transpiler][build] 连续修复失败 {self._consecutive_fix_failures} 次(编译通过但测试失败),回退到函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.RED)
|
|
2057
|
+
if self._reset_to_commit(self._current_function_start_commit):
|
|
2058
|
+
typer.secho("[c2rust-transpiler][build] 已回退到函数开始时的 commit,将重新开始处理该函数", fg=typer.colors.YELLOW)
|
|
2059
|
+
# 返回特殊值,表示需要重新开始
|
|
2060
|
+
return (False, None) # type: ignore
|
|
2061
|
+
else:
|
|
2062
|
+
typer.secho("[c2rust-transpiler][build] 回退失败,继续尝试修复", fg=typer.colors.YELLOW)
|
|
2063
|
+
return (False, False) # 需要继续循环
|
|
2064
|
+
|
|
2065
|
+
def _cargo_build_loop(self) -> Optional[bool]:
|
|
2066
|
+
"""在 crate 目录执行构建与测试:直接运行 cargo test(运行所有测试,不区分项目结构)。失败则最小化修复直到通过或达到上限。"""
|
|
2067
|
+
workspace_root = str(self.crate_dir)
|
|
2068
|
+
test_limit = f"最大重试: {self.test_max_retries if self.test_max_retries > 0 else '无限'}"
|
|
2069
|
+
typer.secho(f"[c2rust-transpiler][build] 工作区={workspace_root},开始构建循环(test,{test_limit})", fg=typer.colors.MAGENTA)
|
|
2070
|
+
test_iter = 0
|
|
2071
|
+
while True:
|
|
2072
|
+
# 运行所有测试(不区分项目结构)
|
|
2073
|
+
# cargo test 会自动编译并运行所有类型的测试:lib tests、bin tests、integration tests、doc tests 等
|
|
2074
|
+
test_iter += 1
|
|
2075
|
+
test_success, need_restart = self._run_cargo_test_and_fix(workspace_root, test_iter)
|
|
2076
|
+
if need_restart is None:
|
|
2077
|
+
return None # 需要回退重新开始
|
|
2078
|
+
if test_success:
|
|
2079
|
+
return True # 测试通过
|
|
1787
2080
|
|
|
1788
2081
|
def _review_and_optimize(self, rec: FnRecord, module: str, rust_sig: str) -> None:
|
|
1789
2082
|
"""
|
|
@@ -1794,15 +2087,45 @@ class Transpiler:
|
|
|
1794
2087
|
def build_review_prompts() -> Tuple[str, str, str]:
|
|
1795
2088
|
sys_p = (
|
|
1796
2089
|
"你是Rust代码审查专家。验收标准:Rust 实现应与原始 C 实现在功能上一致,且不应包含可能导致功能错误的严重问题。\n"
|
|
2090
|
+
"**审查优先级**:严重问题 > 破坏性变更 > 功能一致性 > 文件结构。优先处理可能导致程序崩溃或编译失败的问题。\n"
|
|
2091
|
+
"**审查范围**:主要审查当前函数的实现,相关依赖函数作为辅助参考。\n"
|
|
1797
2092
|
"审查标准(合并了功能一致性和严重问题检查):\n"
|
|
1798
2093
|
"1. 功能一致性检查:\n"
|
|
1799
|
-
" -
|
|
1800
|
-
" -
|
|
2094
|
+
" - **核心功能定义**:核心输入输出、主要功能逻辑是否与 C 实现一致。核心功能指函数的主要目的和预期行为(如'计算哈希值'、'解析字符串'、'压缩数据'等),不包括实现细节;\n"
|
|
2095
|
+
" - **安全改进允许行为不一致**:允许 Rust 实现修复 C 代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用、整数溢出、格式化字符串漏洞等),这些安全改进可能导致行为与 C 实现不一致,但这是允许的,不应被视为功能不一致;\n"
|
|
2096
|
+
" - **忽略语言差异导致的行为不一致**:由于 Rust 和 C 语言的本质差异,以下行为差异是不可避免的,应被忽略:\n"
|
|
2097
|
+
" * 整数溢出处理:Rust 在 debug 模式下会 panic,release 模式下会 wrapping,而 C 是未定义行为;\n"
|
|
2098
|
+
" * 未定义行为:Rust 会避免或明确处理,而 C 可能产生未定义行为;\n"
|
|
2099
|
+
" * 空指针/空引用:Rust 使用 Option<T> 或 Result<T, E> 处理,而 C 可能直接解引用导致崩溃;\n"
|
|
2100
|
+
" * 内存安全:Rust 的借用检查器会阻止某些 C 中允许的不安全操作;\n"
|
|
2101
|
+
" * 错误处理:Rust 使用 Result<T, E> 或 Option<T>,而 C 可能使用错误码或全局 errno;\n"
|
|
1801
2102
|
" - 允许 Rust 实现使用不同的类型设计、错误处理方式、资源管理方式等,只要功能一致即可;\n"
|
|
1802
|
-
"2.
|
|
2103
|
+
"2. 严重问题检查(可能导致功能错误或程序崩溃):\n"
|
|
1803
2104
|
" - 明显的空指针解引用或会导致 panic 的严重错误;\n"
|
|
1804
2105
|
" - 明显的越界访问或会导致程序崩溃的问题;\n"
|
|
2106
|
+
" - 会导致程序无法正常运行的逻辑错误;\n"
|
|
2107
|
+
"3. 破坏性变更检测(对现有代码的影响):\n"
|
|
2108
|
+
" - **允许签名不一致**:允许函数签名、参数数量、参数类型、返回类型等与C实现不一致,只要功能实现了即可。这是Rust转译的正常现象,因为Rust的类型系统和设计理念与C不同;\n"
|
|
2109
|
+
" - **仅检查实际破坏性影响**:只有当函数签名变更确实导致调用方代码无法编译或运行时,才报告为破坏性变更。如果调用方代码已经适配了新签名,或可以通过简单的适配解决,则不应视为破坏性变更;\n"
|
|
2110
|
+
" - **⚠️ 重要:检查测试用例删除**:必须检查代码变更中是否错误删除了测试用例标记(#[test] 或 #[cfg(test)])。如果发现删除了测试用例标记,必须报告为破坏性变更,除非:\n"
|
|
2111
|
+
" * 测试用例被移动到其他位置(在diff中可以看到对应的添加);\n"
|
|
2112
|
+
" * 测试用例是重复的或过时的,确实需要删除;\n"
|
|
2113
|
+
" * 测试用例被重构为其他形式的测试(如集成测试、文档测试等);\n"
|
|
2114
|
+
" - 检查模块导出变更是否会影响其他模块的导入(如 pub 关键字缺失、模块路径变更);\n"
|
|
2115
|
+
" - 检查类型定义变更是否会导致依赖该类型的代码失效(如结构体字段变更、枚举变体变更);\n"
|
|
2116
|
+
" - 检查常量或静态变量变更是否会影响引用该常量的代码;\n"
|
|
2117
|
+
" - **优先使用diff信息**:如果diff中已包含调用方代码信息,优先基于diff判断;只有在diff信息不足时,才使用 read_code 工具读取调用方代码进行验证;\n"
|
|
2118
|
+
"4. 文件结构合理性检查:\n"
|
|
2119
|
+
" - 检查模块文件位置是否符合 Rust 项目约定(如 src/ 目录结构、模块层次);\n"
|
|
2120
|
+
" - 检查文件命名是否符合 Rust 命名规范(如 snake_case、模块文件命名);\n"
|
|
2121
|
+
" - 检查模块组织是否合理(如相关功能是否放在同一模块、模块拆分是否过度或不足);\n"
|
|
2122
|
+
" - 检查模块导出是否合理(如 lib.rs 中的 pub mod 声明是否正确、是否遗漏必要的导出);\n"
|
|
2123
|
+
" - 检查是否存在循环依赖或过度耦合;\n"
|
|
1805
2124
|
"不检查类型匹配、指针可变性、边界检查细节、资源释放细节、内存语义等技术细节(除非会导致功能错误)。\n"
|
|
2125
|
+
"**重要要求:在总结阶段,对于发现的每个问题,必须提供:**\n"
|
|
2126
|
+
"1. 详细的问题描述:明确指出问题所在的位置(文件、函数、行号等)、问题的具体表现、为什么这是一个问题\n"
|
|
2127
|
+
"2. 具体的修复建议:提供详细的修复方案,包括需要修改的代码位置、修改方式、预期效果等\n"
|
|
2128
|
+
"3. 问题分类:使用 [function] 标记功能一致性问题,使用 [critical] 标记严重问题,使用 [breaking] 标记破坏性变更,使用 [structure] 标记文件结构问题\n"
|
|
1806
2129
|
"请在总结阶段详细指出问题和修改建议,但不要尝试修复或修改任何代码,不要输出补丁。"
|
|
1807
2130
|
)
|
|
1808
2131
|
# 附加原始C函数源码片段,供审查作为只读参考
|
|
@@ -1810,8 +2133,68 @@ class Transpiler:
|
|
|
1810
2133
|
# 附加被引用符号上下文与库替代上下文,以及crate目录结构,提供更完整审查背景
|
|
1811
2134
|
callees_ctx = self._collect_callees_context(rec)
|
|
1812
2135
|
librep_ctx = rec.lib_replacement if isinstance(rec.lib_replacement, dict) else None
|
|
1813
|
-
crate_tree =
|
|
1814
|
-
|
|
2136
|
+
crate_tree = dir_tree(self.crate_dir)
|
|
2137
|
+
# 提取编译参数
|
|
2138
|
+
compile_flags = self._extract_compile_flags(rec.file)
|
|
2139
|
+
|
|
2140
|
+
# 获取从初始commit到当前commit的变更作为上下文(每次review都必须获取)
|
|
2141
|
+
commit_diff = ""
|
|
2142
|
+
diff_status = "" # 用于记录diff获取状态
|
|
2143
|
+
if self._current_function_start_commit:
|
|
2144
|
+
current_commit = self._get_crate_commit_hash()
|
|
2145
|
+
if current_commit:
|
|
2146
|
+
if current_commit == self._current_function_start_commit:
|
|
2147
|
+
# commit相同,说明没有变更
|
|
2148
|
+
commit_diff = "(无变更:当前commit与函数开始时的commit相同)"
|
|
2149
|
+
diff_status = "no_change"
|
|
2150
|
+
else:
|
|
2151
|
+
# commit不同,获取diff
|
|
2152
|
+
try:
|
|
2153
|
+
# 注意:transpile()开始时已切换到crate目录,此处无需再次切换
|
|
2154
|
+
commit_diff = get_diff_between_commits(self._current_function_start_commit, current_commit)
|
|
2155
|
+
if commit_diff and not commit_diff.startswith("获取") and not commit_diff.startswith("发生"):
|
|
2156
|
+
# 成功获取diff,限制长度避免上下文过大
|
|
2157
|
+
# 优先使用agent的剩余token数量,回退到输入窗口限制
|
|
2158
|
+
max_diff_chars = None
|
|
2159
|
+
try:
|
|
2160
|
+
# 优先尝试使用已有的agent获取剩余token(更准确,包含对话历史)
|
|
2161
|
+
review_key = f"review::{rec.id}"
|
|
2162
|
+
agent = self._current_agents.get(review_key)
|
|
2163
|
+
if agent:
|
|
2164
|
+
remaining_tokens = agent.get_remaining_token_count()
|
|
2165
|
+
# 使用剩余token的50%作为字符限制(1 token ≈ 4字符,所以 remaining_tokens * 0.5 * 4 = remaining_tokens * 2)
|
|
2166
|
+
max_diff_chars = int(remaining_tokens * 2)
|
|
2167
|
+
if max_diff_chars <= 0:
|
|
2168
|
+
max_diff_chars = None
|
|
2169
|
+
except Exception:
|
|
2170
|
+
pass
|
|
2171
|
+
|
|
2172
|
+
# 回退方案2:使用输入窗口的50%转换为字符数
|
|
2173
|
+
if max_diff_chars is None:
|
|
2174
|
+
max_input_tokens = get_max_input_token_count(self.llm_group)
|
|
2175
|
+
max_diff_chars = max_input_tokens * 2 # 最大输入token数量的一半转换为字符数
|
|
2176
|
+
|
|
2177
|
+
if len(commit_diff) > max_diff_chars:
|
|
2178
|
+
commit_diff = commit_diff[:max_diff_chars] + "\n... (差异内容过长,已截断)"
|
|
2179
|
+
diff_status = "success"
|
|
2180
|
+
else:
|
|
2181
|
+
# 获取失败,保留错误信息
|
|
2182
|
+
diff_status = "error"
|
|
2183
|
+
typer.secho(f"[c2rust-transpiler][review] 获取commit差异失败: {commit_diff}", fg=typer.colors.YELLOW)
|
|
2184
|
+
except Exception as e:
|
|
2185
|
+
commit_diff = f"获取commit差异时发生异常: {str(e)}"
|
|
2186
|
+
diff_status = "error"
|
|
2187
|
+
typer.secho(f"[c2rust-transpiler][review] 获取commit差异失败: {e}", fg=typer.colors.YELLOW)
|
|
2188
|
+
else:
|
|
2189
|
+
# 无法获取当前commit
|
|
2190
|
+
commit_diff = "(无法获取当前commit id)"
|
|
2191
|
+
diff_status = "no_current_commit"
|
|
2192
|
+
else:
|
|
2193
|
+
# 没有保存函数开始时的commit
|
|
2194
|
+
commit_diff = "(未记录函数开始时的commit id)"
|
|
2195
|
+
diff_status = "no_start_commit"
|
|
2196
|
+
|
|
2197
|
+
usr_p_lines = [
|
|
1815
2198
|
f"待审查函数:{rec.qname or rec.name}",
|
|
1816
2199
|
f"建议签名:{rust_sig}",
|
|
1817
2200
|
f"目标模块:{module}",
|
|
@@ -1824,68 +2207,194 @@ class Transpiler:
|
|
|
1824
2207
|
"</C_SOURCE>",
|
|
1825
2208
|
"",
|
|
1826
2209
|
"审查说明(合并审查):",
|
|
2210
|
+
"**审查优先级**:严重问题 > 破坏性变更 > 功能一致性 > 文件结构。优先处理可能导致程序崩溃或编译失败的问题。",
|
|
2211
|
+
"",
|
|
1827
2212
|
"1. 功能一致性:",
|
|
1828
|
-
" -
|
|
1829
|
-
" -
|
|
2213
|
+
" - **核心功能定义**:核心输入输出、主要功能逻辑是否与 C 实现一致。核心功能指函数的主要目的和预期行为(如'计算哈希值'、'解析字符串'、'压缩数据'等),不包括实现细节;",
|
|
2214
|
+
" - **安全改进允许行为不一致**:允许Rust实现修复C代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用、整数溢出、格式化字符串漏洞等),这些安全改进可能导致行为与 C 实现不一致,但这是允许的,不应被视为功能不一致;",
|
|
2215
|
+
" - **忽略语言差异导致的行为不一致**:由于 Rust 和 C 语言的本质差异,以下行为差异是不可避免的,应被忽略:",
|
|
2216
|
+
" * 整数溢出处理:Rust 在 debug 模式下会 panic,release 模式下会 wrapping,而 C 是未定义行为;",
|
|
2217
|
+
" * 未定义行为:Rust 会避免或明确处理,而 C 可能产生未定义行为;",
|
|
2218
|
+
" * 空指针/空引用:Rust 使用 Option<T> 或 Result<T, E> 处理,而 C 可能直接解引用导致崩溃;",
|
|
2219
|
+
" * 内存安全:Rust 的借用检查器会阻止某些 C 中允许的不安全操作;",
|
|
2220
|
+
" * 错误处理:Rust 使用 Result<T, E> 或 Option<T>,而 C 可能使用错误码或全局 errno;",
|
|
1830
2221
|
" - 允许Rust实现使用不同的类型设计、错误处理方式、资源管理方式等,只要功能一致即可;",
|
|
1831
2222
|
"2. 严重问题(可能导致功能错误):",
|
|
1832
2223
|
" - 明显的空指针解引用或会导致 panic 的严重错误;",
|
|
1833
2224
|
" - 明显的越界访问或会导致程序崩溃的问题;",
|
|
2225
|
+
"3. 破坏性变更检测(对现有代码的影响):",
|
|
2226
|
+
" - **允许签名不一致**:允许函数签名、参数数量、参数类型、返回类型等与C实现不一致,只要功能实现了即可。这是Rust转译的正常现象,因为Rust的类型系统和设计理念与C不同;",
|
|
2227
|
+
" - **仅检查实际破坏性影响**:只有当函数签名变更确实导致调用方代码无法编译或运行时,才报告为破坏性变更。如果调用方代码已经适配了新签名,或可以通过简单的适配解决,则不应视为破坏性变更;",
|
|
2228
|
+
" - **⚠️ 重要:检查测试用例删除**:必须检查代码变更中是否错误删除了测试用例标记(#[test] 或 #[cfg(test)])。如果发现删除了测试用例标记,必须报告为破坏性变更,除非:",
|
|
2229
|
+
" * 测试用例被移动到其他位置(在diff中可以看到对应的添加);",
|
|
2230
|
+
" * 测试用例是重复的或过时的,确实需要删除;",
|
|
2231
|
+
" * 测试用例被重构为其他形式的测试(如集成测试、文档测试等);",
|
|
2232
|
+
" - 检查模块导出变更是否会影响其他模块的导入(如 pub 关键字缺失、模块路径变更);",
|
|
2233
|
+
" - 检查类型定义变更是否会导致依赖该类型的代码失效(如结构体字段变更、枚举变体变更);",
|
|
2234
|
+
" - 检查常量或静态变量变更是否会影响引用该常量的代码;",
|
|
2235
|
+
" - **优先使用diff信息**:如果diff中已包含调用方代码信息,优先基于diff判断;只有在diff信息不足时,才使用 read_code 工具读取调用方代码进行验证;",
|
|
2236
|
+
" - 如果该函数是根符号或被其他已转译函数调用,检查调用方代码是否仍能正常编译和使用;如果调用方已经适配了新签名,则不应视为破坏性变更;",
|
|
2237
|
+
"4. 文件结构合理性检查:",
|
|
2238
|
+
" - 检查模块文件位置是否符合 Rust 项目约定(如 src/ 目录结构、模块层次);",
|
|
2239
|
+
" - 检查文件命名是否符合 Rust 命名规范(如 snake_case、模块文件命名);",
|
|
2240
|
+
" - 检查模块组织是否合理(如相关功能是否放在同一模块、模块拆分是否过度或不足);",
|
|
2241
|
+
" - 检查模块导出是否合理(如 lib.rs 中的 pub mod 声明是否正确、是否遗漏必要的导出);",
|
|
2242
|
+
" - 检查是否存在循环依赖或过度耦合;",
|
|
2243
|
+
" - 检查文件大小是否合理(如单个文件是否过大需要拆分,或是否过度拆分导致文件过多);",
|
|
1834
2244
|
"不检查类型匹配、指针可变性、边界检查细节等技术细节(除非会导致功能错误)。",
|
|
1835
2245
|
"",
|
|
2246
|
+
"**重要:问题报告要求**",
|
|
2247
|
+
"对于发现的每个问题,必须在总结中提供:",
|
|
2248
|
+
"1. 详细的问题描述:明确指出问题所在的位置(文件、函数、行号等)、问题的具体表现、为什么这是一个问题",
|
|
2249
|
+
"2. 具体的修复建议:提供详细的修复方案,包括需要修改的代码位置、修改方式、预期效果等",
|
|
2250
|
+
"3. 问题分类:使用 [function] 标记功能一致性问题,使用 [critical] 标记严重问题,使用 [breaking] 标记破坏性变更,使用 [structure] 标记文件结构问题",
|
|
2251
|
+
"示例:",
|
|
2252
|
+
' "[function] 返回值处理缺失:在函数 foo 的第 42 行,当输入参数为负数时,函数没有返回错误码,但 C 实现中会返回 -1。修复建议:在函数开始处添加参数验证,当参数为负数时返回 Result::Err(Error::InvalidInput)。"',
|
|
2253
|
+
' "[critical] 空指针解引用风险:在函数 bar 的第 58 行,直接解引用指针 ptr 而没有检查其是否为 null,可能导致 panic。修复建议:使用 if let Some(value) = ptr.as_ref() 进行空指针检查,或使用 Option<&T> 类型。"',
|
|
2254
|
+
' "[breaking] 函数签名变更导致调用方无法编译:函数 baz 的签名从 `fn baz(x: i32) -> i32` 变更为 `fn baz(x: i64) -> i64`,但调用方代码(src/other.rs:15)仍使用 i32 类型调用,且无法通过简单适配解决,会导致类型不匹配错误。修复建议:保持函数签名与调用方兼容,或同时更新所有调用方代码。注意:如果调用方已经适配了新签名,或可以通过简单的类型转换解决,则不应视为破坏性变更。"',
|
|
2255
|
+
' "[breaking] 测试用例被错误删除:在 diff 中发现删除了测试用例标记 `#[test]` 或 `#[cfg(test)]`(如 src/foo.rs:42),但没有看到对应的添加或移动,这可能导致测试无法运行。修复建议:恢复被删除的测试用例标记,或如果确实需要删除,请说明原因(如测试用例已移动到其他位置、测试用例是重复的等)。"',
|
|
2256
|
+
' "[structure] 模块导出缺失:函数 qux 所在的模块 utils 未在 src/lib.rs 中导出,导致无法从 crate 外部访问。修复建议:在 src/lib.rs 中添加 `pub mod utils;` 声明。"',
|
|
2257
|
+
"",
|
|
1836
2258
|
"被引用符号上下文(如已转译则包含Rust模块信息):",
|
|
1837
2259
|
json.dumps(callees_ctx, ensure_ascii=False, indent=2),
|
|
1838
2260
|
"",
|
|
1839
2261
|
"库替代上下文(若存在):",
|
|
1840
2262
|
json.dumps(librep_ctx, ensure_ascii=False, indent=2),
|
|
2263
|
+
"",
|
|
2264
|
+
*([f"禁用库列表(禁止在实现中使用这些库):{', '.join(self.disabled_libraries)}"] if self.disabled_libraries else []),
|
|
2265
|
+
*([f"根符号要求:此函数是根符号({rec.qname or rec.name}),必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。"] if self._is_root_symbol(rec) else []),
|
|
2266
|
+
]
|
|
2267
|
+
# 添加编译参数(如果存在)
|
|
2268
|
+
if compile_flags:
|
|
2269
|
+
usr_p_lines.extend([
|
|
2270
|
+
"",
|
|
2271
|
+
"C文件编译参数(来自 compile_commands.json):",
|
|
2272
|
+
compile_flags,
|
|
2273
|
+
])
|
|
2274
|
+
usr_p_lines.extend([
|
|
1841
2275
|
"",
|
|
1842
2276
|
"当前crate目录结构(部分):",
|
|
1843
2277
|
"<CRATE_TREE>",
|
|
1844
2278
|
crate_tree,
|
|
1845
2279
|
"</CRATE_TREE>",
|
|
2280
|
+
])
|
|
2281
|
+
|
|
2282
|
+
# 添加commit变更上下文(每次review都必须包含)
|
|
2283
|
+
usr_p_lines.extend([
|
|
2284
|
+
"",
|
|
2285
|
+
"从函数开始到当前的commit变更(用于了解代码变更历史和上下文):",
|
|
2286
|
+
"<COMMIT_DIFF>",
|
|
2287
|
+
commit_diff,
|
|
2288
|
+
"</COMMIT_DIFF>",
|
|
2289
|
+
"",
|
|
2290
|
+
])
|
|
2291
|
+
|
|
2292
|
+
# 根据diff状态添加不同的说明
|
|
2293
|
+
if diff_status == "success":
|
|
2294
|
+
usr_p_lines.extend([
|
|
2295
|
+
"**重要:commit变更上下文说明**",
|
|
2296
|
+
"- 上述diff显示了从函数开始处理时的commit到当前commit之间的所有变更",
|
|
2297
|
+
"- 这些变更可能包括:当前函数的实现、依赖函数的实现、模块结构的调整等",
|
|
2298
|
+
"- **优先使用diff信息进行审查判断**:如果diff中已经包含了足够的信息(如函数实现、签名变更、模块结构等),可以直接基于diff进行审查,无需读取原始文件",
|
|
2299
|
+
"- 只有在diff信息不足或需要查看完整上下文时,才使用 read_code 工具读取原始文件",
|
|
2300
|
+
"- 在审查破坏性变更时,请特别关注这些变更对现有代码的影响",
|
|
2301
|
+
"- 如果发现变更中存在问题(如破坏性变更、文件结构不合理等),请在审查报告中指出",
|
|
2302
|
+
])
|
|
2303
|
+
elif diff_status == "no_change":
|
|
2304
|
+
usr_p_lines.extend([
|
|
2305
|
+
"**注意**:当前commit与函数开始时的commit相同,说明没有代码变更。请使用 read_code 工具读取目标模块文件的最新内容进行审查。",
|
|
2306
|
+
])
|
|
2307
|
+
else:
|
|
2308
|
+
# diff_status 为 "error"、"no_current_commit" 或 "no_start_commit"
|
|
2309
|
+
usr_p_lines.extend([
|
|
2310
|
+
"**注意**:由于无法获取commit差异信息,请使用 read_code 工具读取目标模块文件的最新内容进行审查。",
|
|
2311
|
+
])
|
|
2312
|
+
|
|
2313
|
+
usr_p_lines.extend([
|
|
1846
2314
|
"",
|
|
1847
2315
|
"如需定位或交叉验证 C 符号位置,请使用符号表检索工具:",
|
|
1848
2316
|
"- 工具: read_symbols",
|
|
1849
|
-
"- 参数示例(
|
|
1850
|
-
f" symbols_file: \"{(self.data_dir / 'symbols.jsonl').resolve()}\"",
|
|
1851
|
-
"
|
|
1852
|
-
|
|
2317
|
+
"- 参数示例(JSON):",
|
|
2318
|
+
f" {{\"symbols_file\": \"{(self.data_dir / 'symbols.jsonl').resolve()}\", \"symbols\": [\"{rec.qname or rec.name}\"]}}",
|
|
2319
|
+
"",
|
|
2320
|
+
"**重要:审查要求**",
|
|
2321
|
+
"- **优先使用diff信息**:如果提供了commit差异(COMMIT_DIFF),优先基于diff信息进行审查判断,只有在diff信息不足时才使用 read_code 工具读取原始文件",
|
|
2322
|
+
"- 必须基于最新的代码进行审查,如果使用 read_code 工具,请读取目标模块文件的最新内容",
|
|
2323
|
+
"- 禁止依赖任何历史记忆、之前的审查结论或对话历史进行判断",
|
|
2324
|
+
"- 每次审查都必须基于最新的代码状态(通过diff或read_code获取),确保审查结果反映当前代码的真实状态",
|
|
2325
|
+
"- 结合commit变更上下文(如果提供),全面评估代码变更的影响和合理性",
|
|
1853
2326
|
"",
|
|
1854
|
-
"
|
|
2327
|
+
"请基于提供的diff信息(如果可用)或读取crate中该函数的当前实现进行审查,并准备总结。",
|
|
1855
2328
|
])
|
|
2329
|
+
usr_p = "\n".join(usr_p_lines)
|
|
1856
2330
|
sum_p = (
|
|
1857
|
-
"请仅输出一个 <SUMMARY>
|
|
1858
|
-
"ok: bool
|
|
1859
|
-
"function_issues: [string, ...]
|
|
1860
|
-
"critical_issues: [string, ...]
|
|
2331
|
+
"请仅输出一个 <SUMMARY> 块,块内直接包含 JSON 对象(不需要额外的标签),字段:\n"
|
|
2332
|
+
'"ok": bool // 若满足功能一致且无严重问题、无破坏性变更、文件结构合理,则为 true\n'
|
|
2333
|
+
'"function_issues": [string, ...] // 功能一致性问题,每项以 [function] 开头,必须包含详细的问题描述和修复建议\n'
|
|
2334
|
+
'"critical_issues": [string, ...] // 严重问题(可能导致功能错误),每项以 [critical] 开头,必须包含详细的问题描述和修复建议\n'
|
|
2335
|
+
'"breaking_issues": [string, ...] // 破坏性变更问题(对现有代码的影响),每项以 [breaking] 开头,必须包含详细的问题描述和修复建议\n'
|
|
2336
|
+
'"structure_issues": [string, ...] // 文件结构问题,每项以 [structure] 开头,必须包含详细的问题描述和修复建议\n'
|
|
1861
2337
|
"注意:\n"
|
|
1862
|
-
"- 前置条件:必须在crate中找到该函数的实现(匹配函数名或签名)。若未找到,ok 必须为 false,function_issues 应包含 [function] function not found
|
|
1863
|
-
"-
|
|
1864
|
-
"-
|
|
1865
|
-
"
|
|
2338
|
+
"- 前置条件:必须在crate中找到该函数的实现(匹配函数名或签名)。若未找到,ok 必须为 false,function_issues 应包含 [function] function not found: 详细描述问题位置和如何查找函数实现\n"
|
|
2339
|
+
"- **安全改进允许行为不一致**:若Rust实现修复了C代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用等),即使导致行为与 C 实现不一致,这也是允许的,不应被视为功能不一致;\n"
|
|
2340
|
+
"- **忽略语言差异导致的行为不一致**:由于 Rust 和 C 语言的本质差异(如内存安全、类型系统、错误处理、未定义行为处理等),某些行为差异是不可避免的,这些差异应被忽略,不应被视为功能不一致;\n"
|
|
2341
|
+
"- **允许签名不一致**:允许函数签名、参数数量、参数类型、返回类型等与C实现不一致,只要功能实现了即可。这是Rust转译的正常现象,不应被视为破坏性变更;\n"
|
|
2342
|
+
"- **破坏性变更判断标准**:只有当函数签名变更确实导致调用方代码无法编译或运行时,才报告为破坏性变更。如果调用方代码已经适配了新签名,或可以通过简单的适配解决,则不应视为破坏性变更;\n"
|
|
2343
|
+
"- **⚠️ 重要:测试用例删除检查**:必须检查代码变更中是否错误删除了测试用例标记(#[test] 或 #[cfg(test)])。如果发现删除了测试用例标记且没有合理的理由(如移动到其他位置、重复测试等),必须报告为破坏性变更;\n"
|
|
2344
|
+
"- 若Rust实现使用了不同的实现方式但保持了功能一致,且无严重问题、无破坏性变更、文件结构合理,ok 应为 true\n"
|
|
2345
|
+
"- 仅报告功能不一致、严重问题、破坏性变更和文件结构问题,不报告类型匹配、指针可变性、边界检查细节等技术细节(除非会导致功能错误)\n"
|
|
2346
|
+
"- **重要:每个问题描述必须包含以下内容:**\n"
|
|
2347
|
+
" 1. 问题的详细描述:明确指出问题所在的位置(文件、函数、行号等)、问题的具体表现、为什么这是一个问题\n"
|
|
2348
|
+
" 2. 修复建议:提供具体的修复方案,包括需要修改的代码位置、修改方式、预期效果等\n"
|
|
2349
|
+
" 3. 问题格式:[function]、[critical]、[breaking] 或 [structure] 开头,后跟详细的问题描述和修复建议\n"
|
|
2350
|
+
" 示例格式:\n"
|
|
2351
|
+
' "[function] 返回值处理缺失:在函数 foo 的第 42 行,当输入参数为负数时,函数没有返回错误码,但 C 实现中会返回 -1。修复建议:在函数开始处添加参数验证,当参数为负数时返回 Result::Err(Error::InvalidInput)。"\n'
|
|
2352
|
+
' "[critical] 空指针解引用风险:在函数 bar 的第 58 行,直接解引用指针 ptr 而没有检查其是否为 null,可能导致 panic。修复建议:使用 if let Some(value) = ptr.as_ref() 进行空指针检查,或使用 Option<&T> 类型。"\n'
|
|
2353
|
+
' "[breaking] 函数签名变更导致调用方无法编译:函数 baz 的签名从 `fn baz(x: i32) -> i32` 变更为 `fn baz(x: i64) -> i64`,但调用方代码(src/other.rs:15)仍使用 i32 类型调用,且无法通过简单适配解决,会导致类型不匹配错误。修复建议:保持函数签名与调用方兼容,或同时更新所有调用方代码。注意:如果调用方已经适配了新签名,或可以通过简单的类型转换解决,则不应视为破坏性变更。"\n'
|
|
2354
|
+
' "[structure] 模块导出缺失:函数 qux 所在的模块 utils 未在 src/lib.rs 中导出,导致无法从 crate 外部访问。修复建议:在 src/lib.rs 中添加 `pub mod utils;` 声明。"\n'
|
|
2355
|
+
"请严格按以下格式输出(JSON格式,支持jsonnet语法如尾随逗号、注释、|||分隔符多行字符串等):\n"
|
|
2356
|
+
"<SUMMARY>\n{\n \"ok\": true,\n \"function_issues\": [],\n \"critical_issues\": [],\n \"breaking_issues\": [],\n \"structure_issues\": []\n}\n</SUMMARY>"
|
|
1866
2357
|
)
|
|
2358
|
+
# 在 usr_p 和 sum_p 中追加附加说明(sys_p 通常不需要)
|
|
2359
|
+
usr_p = self._append_additional_notes(usr_p)
|
|
2360
|
+
sum_p = self._append_additional_notes(sum_p)
|
|
1867
2361
|
return sys_p, usr_p, sum_p
|
|
1868
2362
|
|
|
1869
2363
|
i = 0
|
|
1870
2364
|
max_iterations = self.review_max_iterations
|
|
1871
2365
|
# 复用 Review Agent(仅在本函数生命周期内构建一次)
|
|
2366
|
+
# 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
|
|
2367
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
1872
2368
|
review_key = f"review::{rec.id}"
|
|
1873
|
-
sys_p_init,
|
|
2369
|
+
sys_p_init, _, sum_p_init = build_review_prompts() # 只获取一次 sys_p 和 sum_p,usr_p 每次重新构建
|
|
2370
|
+
|
|
2371
|
+
# 获取函数信息用于 Agent name
|
|
2372
|
+
fn_name = rec.qname or rec.name or f"fn_{rec.id}"
|
|
2373
|
+
agent_name = f"C2Rust-Review-Agent({fn_name})"
|
|
2374
|
+
|
|
1874
2375
|
if self._current_agents.get(review_key) is None:
|
|
1875
|
-
|
|
2376
|
+
review_agent = Agent(
|
|
1876
2377
|
system_prompt=sys_p_init,
|
|
1877
|
-
name=
|
|
2378
|
+
name=agent_name,
|
|
1878
2379
|
model_group=self.llm_group,
|
|
1879
2380
|
summary_prompt=sum_p_init,
|
|
1880
2381
|
need_summary=True,
|
|
1881
2382
|
auto_complete=True,
|
|
1882
|
-
use_tools=["execute_script", "read_code", "
|
|
1883
|
-
plan=False,
|
|
2383
|
+
use_tools=["execute_script", "read_code", "read_symbols"],
|
|
1884
2384
|
non_interactive=self.non_interactive,
|
|
1885
2385
|
use_methodology=False,
|
|
1886
2386
|
use_analysis=False,
|
|
1887
|
-
disable_file_edit=True,
|
|
1888
2387
|
)
|
|
2388
|
+
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
2389
|
+
review_agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
2390
|
+
review_agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
2391
|
+
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
2392
|
+
agent_id = id(review_agent)
|
|
2393
|
+
agent_key = f"agent_{agent_id}"
|
|
2394
|
+
initial_commit = self._get_crate_commit_hash()
|
|
2395
|
+
if initial_commit:
|
|
2396
|
+
self._agent_before_commits[agent_key] = initial_commit
|
|
2397
|
+
self._current_agents[review_key] = review_agent
|
|
1889
2398
|
|
|
1890
2399
|
# 0表示无限重试,否则限制迭代次数
|
|
1891
2400
|
use_direct_model_review = False # 标记是否使用直接模型调用
|
|
@@ -1893,101 +2402,117 @@ class Transpiler:
|
|
|
1893
2402
|
parse_error_msg: Optional[str] = None # 保存上一次的YAML解析错误信息
|
|
1894
2403
|
while max_iterations == 0 or i < max_iterations:
|
|
1895
2404
|
agent = self._current_agents[review_key]
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2405
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
2406
|
+
|
|
2407
|
+
# 每次迭代都重新获取最新的 diff(从保存的 commit 到当前的 HEAD)
|
|
2408
|
+
# 重新构建 user prompt,包含最新的 diff
|
|
2409
|
+
_, usr_p_current, _ = build_review_prompts() # 重新构建,获取最新的 diff
|
|
2410
|
+
|
|
2411
|
+
if i > 0:
|
|
2412
|
+
# 修复后的审查,添加代码已更新的提示
|
|
2413
|
+
code_changed_notice = "\n".join([
|
|
2414
|
+
"",
|
|
2415
|
+
"【重要:代码已更新】",
|
|
2416
|
+
f"在本次审查之前(第 {i} 次迭代),已根据审查意见对代码进行了修复和优化。",
|
|
2417
|
+
"目标函数的实现已经发生变化,包括但不限于:",
|
|
2418
|
+
"- 函数实现逻辑的修改",
|
|
2419
|
+
"- 类型和签名的调整",
|
|
2420
|
+
"- 依赖关系的更新",
|
|
2421
|
+
"- 错误处理的改进",
|
|
2422
|
+
"",
|
|
2423
|
+
"**审查要求:**",
|
|
2424
|
+
"- **优先使用diff信息**:如果提供了最新的commit差异(COMMIT_DIFF),优先基于diff信息进行审查判断,只有在diff信息不足时才使用 read_code 工具读取原始文件",
|
|
2425
|
+
"- 如果必须使用 read_code 工具,请读取目标模块文件的最新内容",
|
|
2426
|
+
"- **禁止基于之前的审查结果、对话历史或任何缓存信息进行判断**",
|
|
2427
|
+
"- 必须基于最新的代码状态(通过diff或read_code获取)进行审查评估",
|
|
2428
|
+
"",
|
|
2429
|
+
"如果diff信息充足,可以直接基于diff进行审查;如果diff信息不足,请使用 read_code 工具读取最新代码。",
|
|
2430
|
+
"",
|
|
2431
|
+
])
|
|
2432
|
+
usr_p_with_notice = usr_p_current + code_changed_notice
|
|
2433
|
+
composed_prompt = self._compose_prompt_with_context(usr_p_with_notice)
|
|
2434
|
+
# 修复后必须使用 Agent.run(),不能使用直接模型调用(因为需要工具调用)
|
|
2435
|
+
use_direct_model_review = False
|
|
2436
|
+
else:
|
|
2437
|
+
composed_prompt = self._compose_prompt_with_context(usr_p_current)
|
|
2438
|
+
|
|
2439
|
+
if use_direct_model_review:
|
|
2440
|
+
# 格式解析失败后,直接调用模型接口
|
|
2441
|
+
# 构造包含摘要提示词和具体错误信息的完整提示
|
|
2442
|
+
error_guidance = ""
|
|
2443
|
+
# 检查上一次的解析结果
|
|
2444
|
+
if parse_error_msg:
|
|
2445
|
+
# 如果有JSON解析错误,优先反馈
|
|
2446
|
+
error_guidance = (
|
|
2447
|
+
f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n"
|
|
2448
|
+
f"- JSON解析失败: {parse_error_msg}\n\n"
|
|
2449
|
+
f"请确保输出的JSON格式正确,包括正确的引号、逗号、大括号等。JSON 对象必须包含字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)、breaking_issues(字符串数组)、structure_issues(字符串数组)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
|
|
2450
|
+
)
|
|
2451
|
+
elif parse_failed:
|
|
2452
|
+
error_guidance = (
|
|
2453
|
+
"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n"
|
|
2454
|
+
"- 无法从摘要中解析出有效的 JSON 对象\n\n"
|
|
2455
|
+
"请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内直接包含 JSON 对象(不需要额外的标签),字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)、breaking_issues(字符串数组)、structure_issues(字符串数组)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
|
|
2456
|
+
)
|
|
1923
2457
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- YAML解析失败: {parse_error_msg}\n\n请确保输出的YAML格式正确,包括正确的缩进、引号、冒号等。YAML 对象必须包含字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)。"
|
|
1932
|
-
elif parse_failed:
|
|
1933
|
-
error_guidance = "\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- 无法从摘要中解析出有效的 YAML 对象\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内为单个 <yaml> 对象,字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)。"
|
|
1934
|
-
|
|
1935
|
-
full_prompt = f"{composed_prompt}{error_guidance}\n\n{sum_p_init}"
|
|
1936
|
-
try:
|
|
1937
|
-
response = agent.model.chat_until_success(full_prompt) # type: ignore
|
|
1938
|
-
summary = str(response or "")
|
|
1939
|
-
except Exception as e:
|
|
1940
|
-
typer.secho(f"[c2rust-transpiler][review] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
|
|
1941
|
-
summary = str(agent.run(composed_prompt) or "")
|
|
1942
|
-
else:
|
|
1943
|
-
# 第一次使用 run(),让 Agent 完整运行(可能使用工具)
|
|
2458
|
+
full_prompt = f"{composed_prompt}{error_guidance}\n\n{sum_p_init}"
|
|
2459
|
+
typer.secho(f"[c2rust-transpiler][review] 直接调用模型接口修复格式错误(第 {i+1} 次重试)", fg=typer.colors.YELLOW)
|
|
2460
|
+
try:
|
|
2461
|
+
response = agent.model.chat_until_success(full_prompt) # type: ignore
|
|
2462
|
+
summary = str(response or "")
|
|
2463
|
+
except Exception as e:
|
|
2464
|
+
typer.secho(f"[c2rust-transpiler][review] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
|
|
1944
2465
|
summary = str(agent.run(composed_prompt) or "")
|
|
1945
|
-
|
|
1946
|
-
|
|
2466
|
+
else:
|
|
2467
|
+
# 第一次使用 run(),让 Agent 完整运行(可能使用工具)
|
|
2468
|
+
summary = str(agent.run(composed_prompt) or "")
|
|
1947
2469
|
|
|
1948
|
-
# 解析
|
|
1949
|
-
verdict, parse_error_review =
|
|
2470
|
+
# 解析 JSON 格式的审查结果
|
|
2471
|
+
verdict, parse_error_review = extract_json_from_summary(summary)
|
|
1950
2472
|
parse_failed = False
|
|
1951
2473
|
parse_error_msg = None
|
|
1952
2474
|
if parse_error_review:
|
|
1953
|
-
#
|
|
2475
|
+
# JSON解析失败
|
|
1954
2476
|
parse_failed = True
|
|
1955
2477
|
parse_error_msg = parse_error_review
|
|
1956
|
-
typer.secho(f"[c2rust-transpiler][review]
|
|
2478
|
+
typer.secho(f"[c2rust-transpiler][review] JSON解析失败: {parse_error_review}", fg=typer.colors.YELLOW)
|
|
1957
2479
|
# 兼容旧格式:尝试解析纯文本 OK
|
|
1958
2480
|
m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", summary, flags=re.IGNORECASE)
|
|
1959
2481
|
content = (m.group(1).strip() if m else summary.strip()).upper()
|
|
1960
2482
|
if content == "OK":
|
|
1961
|
-
verdict = {"ok": True, "function_issues": [], "critical_issues": []}
|
|
2483
|
+
verdict = {"ok": True, "function_issues": [], "critical_issues": [], "breaking_issues": [], "structure_issues": []}
|
|
1962
2484
|
parse_failed = False # 兼容格式成功,不算解析失败
|
|
1963
2485
|
parse_error_msg = None
|
|
1964
2486
|
else:
|
|
1965
|
-
#
|
|
1966
|
-
verdict = {"ok": False, "function_issues": [content], "critical_issues": []}
|
|
1967
|
-
# 格式解析失败,后续迭代使用直接模型调用
|
|
2487
|
+
# 无法解析,立即重试:设置标志并继续循环
|
|
1968
2488
|
use_direct_model_review = True
|
|
2489
|
+
# 继续循环,立即重试
|
|
2490
|
+
continue
|
|
1969
2491
|
elif not isinstance(verdict, dict):
|
|
1970
2492
|
parse_failed = True
|
|
1971
2493
|
# 兼容旧格式:尝试解析纯文本 OK
|
|
1972
2494
|
m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", summary, flags=re.IGNORECASE)
|
|
1973
2495
|
content = (m.group(1).strip() if m else summary.strip()).upper()
|
|
1974
2496
|
if content == "OK":
|
|
1975
|
-
verdict = {"ok": True, "function_issues": [], "critical_issues": []}
|
|
2497
|
+
verdict = {"ok": True, "function_issues": [], "critical_issues": [], "breaking_issues": [], "structure_issues": []}
|
|
1976
2498
|
parse_failed = False # 兼容格式成功,不算解析失败
|
|
1977
2499
|
else:
|
|
1978
|
-
#
|
|
1979
|
-
verdict = {"ok": False, "function_issues": [content], "critical_issues": []}
|
|
1980
|
-
# 格式解析失败,后续迭代使用直接模型调用
|
|
2500
|
+
# 无法解析,立即重试:设置标志并继续循环
|
|
1981
2501
|
use_direct_model_review = True
|
|
2502
|
+
parse_error_msg = f"无法从摘要中解析出有效的 JSON 对象,得到的内容类型为: {type(verdict).__name__}"
|
|
2503
|
+
# 继续循环,立即重试
|
|
2504
|
+
continue
|
|
1982
2505
|
|
|
1983
2506
|
ok = bool(verdict.get("ok") is True)
|
|
1984
2507
|
function_issues = verdict.get("function_issues") if isinstance(verdict.get("function_issues"), list) else []
|
|
1985
2508
|
critical_issues = verdict.get("critical_issues") if isinstance(verdict.get("critical_issues"), list) else []
|
|
1986
|
-
|
|
2509
|
+
breaking_issues = verdict.get("breaking_issues") if isinstance(verdict.get("breaking_issues"), list) else []
|
|
2510
|
+
structure_issues = verdict.get("structure_issues") if isinstance(verdict.get("structure_issues"), list) else []
|
|
1987
2511
|
|
|
1988
|
-
typer.secho(f"[c2rust-transpiler][review][iter={i+1}] verdict ok={ok}, function_issues={len(function_issues)}, critical_issues={len(critical_issues)}", fg=typer.colors.CYAN)
|
|
2512
|
+
typer.secho(f"[c2rust-transpiler][review][iter={i+1}] verdict ok={ok}, function_issues={len(function_issues)}, critical_issues={len(critical_issues)}, breaking_issues={len(breaking_issues)}, structure_issues={len(structure_issues)}", fg=typer.colors.CYAN)
|
|
1989
2513
|
|
|
1990
|
-
|
|
2514
|
+
# 如果 ok 为 true,表示审查通过(功能一致且无严重问题、无破坏性变更、文件结构合理),直接返回,不触发修复
|
|
2515
|
+
if ok:
|
|
1991
2516
|
limit_info = f" (上限: {max_iterations if max_iterations > 0 else '无限'})"
|
|
1992
2517
|
typer.secho(f"[c2rust-transpiler][review] 代码审查通过{limit_info} (共 {i+1} 次迭代)。", fg=typer.colors.GREEN)
|
|
1993
2518
|
# 记录审查结果到进度
|
|
@@ -1995,14 +2520,18 @@ class Transpiler:
|
|
|
1995
2520
|
cur = self.progress.get("current") or {}
|
|
1996
2521
|
cur["review"] = {
|
|
1997
2522
|
"ok": True,
|
|
1998
|
-
"function_issues":
|
|
1999
|
-
"critical_issues":
|
|
2523
|
+
"function_issues": list(function_issues),
|
|
2524
|
+
"critical_issues": list(critical_issues),
|
|
2525
|
+
"breaking_issues": list(breaking_issues),
|
|
2526
|
+
"structure_issues": list(structure_issues),
|
|
2000
2527
|
"iterations": i + 1,
|
|
2001
2528
|
}
|
|
2002
2529
|
metrics = cur.get("metrics") or {}
|
|
2003
2530
|
metrics["review_iterations"] = i + 1
|
|
2004
|
-
metrics["function_issues"] =
|
|
2005
|
-
metrics["type_issues"] =
|
|
2531
|
+
metrics["function_issues"] = len(function_issues)
|
|
2532
|
+
metrics["type_issues"] = len(critical_issues)
|
|
2533
|
+
metrics["breaking_issues"] = len(breaking_issues)
|
|
2534
|
+
metrics["structure_issues"] = len(structure_issues)
|
|
2006
2535
|
cur["metrics"] = metrics
|
|
2007
2536
|
self.progress["current"] = cur
|
|
2008
2537
|
self._save_progress()
|
|
@@ -2011,12 +2540,16 @@ class Transpiler:
|
|
|
2011
2540
|
return
|
|
2012
2541
|
|
|
2013
2542
|
# 需要优化:提供详细上下文背景,并明确审查意见仅针对 Rust crate,不修改 C 源码
|
|
2014
|
-
crate_tree =
|
|
2543
|
+
crate_tree = dir_tree(self.crate_dir)
|
|
2015
2544
|
issues_text = "\n".join([
|
|
2016
2545
|
"功能一致性问题:" if function_issues else "",
|
|
2017
2546
|
*[f" - {issue}" for issue in function_issues],
|
|
2018
2547
|
"严重问题(可能导致功能错误):" if critical_issues else "",
|
|
2019
2548
|
*[f" - {issue}" for issue in critical_issues],
|
|
2549
|
+
"破坏性变更问题(对现有代码的影响):" if breaking_issues else "",
|
|
2550
|
+
*[f" - {issue}" for issue in breaking_issues],
|
|
2551
|
+
"文件结构问题:" if structure_issues else "",
|
|
2552
|
+
*[f" - {issue}" for issue in structure_issues],
|
|
2020
2553
|
])
|
|
2021
2554
|
fix_prompt = "\n".join([
|
|
2022
2555
|
"请根据以下审查结论对目标函数进行最小优化(保留结构与意图,不进行大范围重构):",
|
|
@@ -2039,6 +2572,9 @@ class Transpiler:
|
|
|
2039
2572
|
"- 如审查问题涉及缺失/未实现的被调函数或依赖,请阅读其 C 源码并在本次一并补齐等价的 Rust 实现;必要时在合理模块新增函数或引入精确 use;",
|
|
2040
2573
|
"- 禁止使用 todo!/unimplemented! 作为占位;",
|
|
2041
2574
|
"- 可使用工具 read_symbols/read_code 获取依赖符号的 C 源码与位置以辅助实现;仅精确导入所需符号(禁止通配);",
|
|
2575
|
+
"- 注释规范:所有代码注释(包括文档注释、行内注释、块注释等)必须使用中文;",
|
|
2576
|
+
*([f"- **禁用库约束**:禁止在优化中使用以下库:{', '.join(self.disabled_libraries)}。如果这些库在 Cargo.toml 中已存在,请移除相关依赖;如果优化需要使用这些库的功能,请使用标准库或其他允许的库替代。"] if self.disabled_libraries else []),
|
|
2577
|
+
*([f"- **根符号要求**:此函数是根符号({rec.qname or rec.name}),必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。"] if self._is_root_symbol(rec) else []),
|
|
2042
2578
|
"",
|
|
2043
2579
|
"【重要:依赖检查与实现要求】",
|
|
2044
2580
|
"在优化函数之前,请务必检查以下内容:",
|
|
@@ -2049,7 +2585,6 @@ class Transpiler:
|
|
|
2049
2585
|
" - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
|
|
2050
2586
|
" - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
|
|
2051
2587
|
" - 可以使用 read_code 工具读取相关模块文件进行检查",
|
|
2052
|
-
" - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
|
|
2053
2588
|
"3. 对于未实现的依赖函数:",
|
|
2054
2589
|
" - 使用 read_symbols 工具获取其 C 源码和符号信息",
|
|
2055
2590
|
" - 使用 read_code 工具读取其 C 源码实现",
|
|
@@ -2067,17 +2602,26 @@ class Transpiler:
|
|
|
2067
2602
|
"",
|
|
2068
2603
|
"请仅以补丁形式输出修改,避免冗余解释。",
|
|
2069
2604
|
])
|
|
2070
|
-
#
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2605
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
2606
|
+
# 记录运行前的 commit
|
|
2607
|
+
before_commit = self._get_crate_commit_hash()
|
|
2608
|
+
ca = self._get_code_agent()
|
|
2609
|
+
limit_info = f"/{max_iterations}" if max_iterations > 0 else "/∞"
|
|
2610
|
+
fix_prompt_with_notes = self._append_additional_notes(fix_prompt)
|
|
2611
|
+
ca.run(self._compose_prompt_with_context(fix_prompt_with_notes), prefix=f"[c2rust-transpiler][review-fix iter={i+1}{limit_info}]", suffix="")
|
|
2612
|
+
|
|
2613
|
+
# 检测并处理测试代码删除
|
|
2614
|
+
if self._check_and_handle_test_deletion(before_commit, ca):
|
|
2615
|
+
# 如果回退了,需要重新运行 agent
|
|
2616
|
+
typer.secho(f"[c2rust-transpiler][review-fix] 检测到测试代码删除问题,已回退,重新运行 agent (iter={i+1})", fg=typer.colors.YELLOW)
|
|
2617
|
+
before_commit = self._get_crate_commit_hash()
|
|
2618
|
+
ca.run(self._compose_prompt_with_context(fix_prompt_with_notes), prefix=f"[c2rust-transpiler][review-fix iter={i+1}{limit_info}][retry]", suffix="")
|
|
2619
|
+
# 再次检测
|
|
2620
|
+
if self._check_and_handle_test_deletion(before_commit, ca):
|
|
2621
|
+
typer.secho(f"[c2rust-transpiler][review-fix] 再次检测到测试代码删除问题,已回退 (iter={i+1})", fg=typer.colors.RED)
|
|
2622
|
+
|
|
2623
|
+
# 优化后进行一次构建验证;若未通过则进入构建修复循环,直到通过为止
|
|
2624
|
+
self._cargo_build_loop()
|
|
2081
2625
|
|
|
2082
2626
|
# 记录本次审查结果
|
|
2083
2627
|
try:
|
|
@@ -2086,11 +2630,15 @@ class Transpiler:
|
|
|
2086
2630
|
"ok": False,
|
|
2087
2631
|
"function_issues": list(function_issues),
|
|
2088
2632
|
"critical_issues": list(critical_issues),
|
|
2633
|
+
"breaking_issues": list(breaking_issues),
|
|
2634
|
+
"structure_issues": list(structure_issues),
|
|
2089
2635
|
"iterations": i + 1,
|
|
2090
2636
|
}
|
|
2091
2637
|
metrics = cur.get("metrics") or {}
|
|
2092
2638
|
metrics["function_issues"] = len(function_issues)
|
|
2093
2639
|
metrics["type_issues"] = len(critical_issues)
|
|
2640
|
+
metrics["breaking_issues"] = len(breaking_issues)
|
|
2641
|
+
metrics["structure_issues"] = len(structure_issues)
|
|
2094
2642
|
cur["metrics"] = metrics
|
|
2095
2643
|
self.progress["current"] = cur
|
|
2096
2644
|
self._save_progress()
|
|
@@ -2122,182 +2670,293 @@ class Transpiler:
|
|
|
2122
2670
|
# 写入 JSONL 映射(带源位置,用于区分同名符号)
|
|
2123
2671
|
self.symbol_map.add(rec, module, rust_symbol or (rec.name or f"fn_{rec.id}"))
|
|
2124
2672
|
|
|
2673
|
+
# 获取当前 commit id 并记录
|
|
2674
|
+
current_commit = self._get_crate_commit_hash()
|
|
2675
|
+
|
|
2125
2676
|
# 更新进度:已转换集合
|
|
2126
2677
|
converted = self.progress.get("converted") or []
|
|
2127
2678
|
if rec.id not in converted:
|
|
2128
2679
|
converted.append(rec.id)
|
|
2129
2680
|
self.progress["converted"] = converted
|
|
2130
2681
|
self.progress["current"] = None
|
|
2682
|
+
|
|
2683
|
+
# 记录每个已转换函数的 commit id
|
|
2684
|
+
converted_commits = self.progress.get("converted_commits") or {}
|
|
2685
|
+
if current_commit:
|
|
2686
|
+
converted_commits[str(rec.id)] = current_commit
|
|
2687
|
+
self.progress["converted_commits"] = converted_commits
|
|
2688
|
+
typer.secho(f"[c2rust-transpiler][progress] 已记录函数 {rec.id} 的 commit: {current_commit}", fg=typer.colors.CYAN)
|
|
2689
|
+
|
|
2131
2690
|
self._save_progress()
|
|
2132
2691
|
|
|
2133
2692
|
def transpile(self) -> None:
|
|
2134
2693
|
"""主流程"""
|
|
2135
2694
|
typer.secho("[c2rust-transpiler][start] 开始转译", fg=typer.colors.BLUE)
|
|
2136
|
-
#
|
|
2695
|
+
# 切换到 crate 根目录,整个转译过程都在此目录下执行
|
|
2696
|
+
prev_cwd = os.getcwd()
|
|
2137
2697
|
try:
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
typer.secho("[c2rust-transpiler] 未找到翻译步骤。", fg=typer.colors.YELLOW)
|
|
2171
|
-
return
|
|
2172
|
-
|
|
2173
|
-
# 构建自包含 order 索引(id -> FnRecord,name/qname -> id)
|
|
2174
|
-
self._load_order_index(order_path)
|
|
2175
|
-
|
|
2176
|
-
# 扁平化顺序,按单个函数处理(保持原有顺序)
|
|
2177
|
-
seq: List[int] = []
|
|
2178
|
-
for grp in steps:
|
|
2179
|
-
seq.extend(grp)
|
|
2698
|
+
os.chdir(str(self.crate_dir))
|
|
2699
|
+
typer.secho(f"[c2rust-transpiler][start] 已切换到 crate 目录: {os.getcwd()}", fg=typer.colors.BLUE)
|
|
2700
|
+
# 准确性兜底:在未执行 prepare 的情况下,确保 crate 目录与最小 Cargo 配置存在
|
|
2701
|
+
try:
|
|
2702
|
+
cd = self.crate_dir.resolve()
|
|
2703
|
+
cd.mkdir(parents=True, exist_ok=True)
|
|
2704
|
+
cargo = cd / "Cargo.toml"
|
|
2705
|
+
src_dir = cd / "src"
|
|
2706
|
+
lib_rs = src_dir / "lib.rs"
|
|
2707
|
+
# 最小 Cargo.toml(不覆盖已有),edition 使用 2021 以兼容更广环境
|
|
2708
|
+
if not cargo.exists():
|
|
2709
|
+
pkg_name = cd.name
|
|
2710
|
+
content = (
|
|
2711
|
+
f'[package]\nname = "{pkg_name}"\nversion = "0.1.0"\nedition = "2021"\n\n'
|
|
2712
|
+
'[lib]\npath = "src/lib.rs"\n'
|
|
2713
|
+
)
|
|
2714
|
+
try:
|
|
2715
|
+
cargo.write_text(content, encoding="utf-8")
|
|
2716
|
+
typer.secho(f"[c2rust-transpiler][init] created Cargo.toml at {cargo}", fg=typer.colors.GREEN)
|
|
2717
|
+
except Exception:
|
|
2718
|
+
pass
|
|
2719
|
+
# 确保 src/lib.rs 存在
|
|
2720
|
+
src_dir.mkdir(parents=True, exist_ok=True)
|
|
2721
|
+
if not lib_rs.exists():
|
|
2722
|
+
try:
|
|
2723
|
+
lib_rs.write_text("// Auto-created by c2rust transpiler\n", encoding="utf-8")
|
|
2724
|
+
typer.secho(f"[c2rust-transpiler][init] created src/lib.rs at {lib_rs}", fg=typer.colors.GREEN)
|
|
2725
|
+
except Exception:
|
|
2726
|
+
pass
|
|
2727
|
+
except Exception:
|
|
2728
|
+
# 保持稳健,失败不阻塞主流程
|
|
2729
|
+
pass
|
|
2180
2730
|
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2731
|
+
order_path = ensure_order_file(self.project_root)
|
|
2732
|
+
steps = iter_order_steps(order_path)
|
|
2733
|
+
if not steps:
|
|
2734
|
+
typer.secho("[c2rust-transpiler] 未找到翻译步骤。", fg=typer.colors.YELLOW)
|
|
2735
|
+
return
|
|
2184
2736
|
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
continue
|
|
2188
|
-
rec = self.fn_index_by_id.get(fid)
|
|
2189
|
-
if not rec:
|
|
2190
|
-
continue
|
|
2191
|
-
if self._should_skip(rec):
|
|
2192
|
-
typer.secho(f"[c2rust-transpiler][skip] 跳过 {rec.qname or rec.name} (id={rec.id}) 位于 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.YELLOW)
|
|
2193
|
-
continue
|
|
2737
|
+
# 构建自包含 order 索引(id -> FnRecord,name/qname -> id)
|
|
2738
|
+
self._load_order_index(order_path)
|
|
2194
2739
|
|
|
2195
|
-
#
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
# 若缺少源码片段且缺乏签名/参数信息,则跳过本函数,记录进度以便后续处理
|
|
2201
|
-
if not c_code and not (getattr(rec, "signature", "") or getattr(rec, "params", None)):
|
|
2202
|
-
skipped = self.progress.get("skipped_missing_source") or []
|
|
2203
|
-
if rec.id not in skipped:
|
|
2204
|
-
skipped.append(rec.id)
|
|
2205
|
-
self.progress["skipped_missing_source"] = skipped
|
|
2206
|
-
typer.secho(f"[c2rust-transpiler] 跳过:缺少源码与签名信息 -> {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.YELLOW)
|
|
2207
|
-
self._save_progress()
|
|
2208
|
-
continue
|
|
2209
|
-
# 1) 规划:模块路径与Rust签名
|
|
2210
|
-
typer.secho(f"[c2rust-transpiler][plan] 正在规划模块与签名: {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.CYAN)
|
|
2211
|
-
module, rust_sig = self._plan_module_and_signature(rec, c_code)
|
|
2212
|
-
typer.secho(f"[c2rust-transpiler][plan] 已选择 模块={module}, 签名={rust_sig}", fg=typer.colors.CYAN)
|
|
2740
|
+
# 扁平化顺序,按单个函数处理(保持原有顺序)
|
|
2741
|
+
seq: List[int] = []
|
|
2742
|
+
for grp in steps:
|
|
2743
|
+
seq.extend(grp)
|
|
2213
2744
|
|
|
2214
|
-
#
|
|
2215
|
-
self.
|
|
2216
|
-
|
|
2745
|
+
# 若支持 resume,则跳过 progress['converted'] 中已完成的
|
|
2746
|
+
done: Set[int] = set(self.progress.get("converted") or [])
|
|
2747
|
+
# 计算需要处理的函数总数(排除已完成的)
|
|
2748
|
+
total_to_process = len([fid for fid in seq if fid not in done])
|
|
2749
|
+
current_index = 0
|
|
2750
|
+
|
|
2751
|
+
# 恢复时,reset 到最后一个已转换函数的 commit id
|
|
2752
|
+
if self.resume and done:
|
|
2753
|
+
converted_commits = self.progress.get("converted_commits") or {}
|
|
2754
|
+
if converted_commits:
|
|
2755
|
+
# 找到最后一个已转换函数的 commit id
|
|
2756
|
+
last_commit = None
|
|
2757
|
+
for fid in reversed(seq):
|
|
2758
|
+
if fid in done:
|
|
2759
|
+
commit_id = converted_commits.get(str(fid))
|
|
2760
|
+
if commit_id:
|
|
2761
|
+
last_commit = commit_id
|
|
2762
|
+
break
|
|
2763
|
+
|
|
2764
|
+
if last_commit:
|
|
2765
|
+
current_commit = self._get_crate_commit_hash()
|
|
2766
|
+
if current_commit != last_commit:
|
|
2767
|
+
typer.secho(f"[c2rust-transpiler][resume] 检测到代码状态不一致,正在 reset 到最后一个已转换函数的 commit: {last_commit}", fg=typer.colors.YELLOW)
|
|
2768
|
+
if self._reset_to_commit(last_commit):
|
|
2769
|
+
typer.secho(f"[c2rust-transpiler][resume] 已 reset 到 commit: {last_commit}", fg=typer.colors.GREEN)
|
|
2770
|
+
else:
|
|
2771
|
+
typer.secho("[c2rust-transpiler][resume] reset 失败,继续使用当前代码状态", fg=typer.colors.YELLOW)
|
|
2772
|
+
else:
|
|
2773
|
+
typer.secho("[c2rust-transpiler][resume] 代码状态一致,无需 reset", fg=typer.colors.CYAN)
|
|
2774
|
+
|
|
2775
|
+
typer.secho(f"[c2rust-transpiler][order] 顺序信息: 步骤数={len(steps)} 总ID={sum(len(g) for g in steps)} 已转换={len(done)} 待处理={total_to_process}", fg=typer.colors.BLUE)
|
|
2217
2776
|
|
|
2218
|
-
|
|
2219
|
-
|
|
2777
|
+
for fid in seq:
|
|
2778
|
+
if fid in done:
|
|
2779
|
+
continue
|
|
2780
|
+
rec = self.fn_index_by_id.get(fid)
|
|
2781
|
+
if not rec:
|
|
2782
|
+
continue
|
|
2783
|
+
if self._should_skip(rec):
|
|
2784
|
+
typer.secho(f"[c2rust-transpiler][skip] 跳过 {rec.qname or rec.name} (id={rec.id}) 位于 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.YELLOW)
|
|
2785
|
+
continue
|
|
2220
2786
|
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
#
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2787
|
+
# 更新进度索引
|
|
2788
|
+
current_index += 1
|
|
2789
|
+
progress_info = f"({current_index}/{total_to_process})" if total_to_process > 0 else ""
|
|
2790
|
+
|
|
2791
|
+
# 在每个函数开始转译前执行 cargo fmt
|
|
2792
|
+
workspace_root = str(self.crate_dir)
|
|
2793
|
+
self._run_cargo_fmt(workspace_root)
|
|
2794
|
+
|
|
2795
|
+
# 读取C函数源码
|
|
2796
|
+
typer.secho(f"[c2rust-transpiler][read] {progress_info} 读取 C 源码: {rec.qname or rec.name} (id={rec.id}) 来自 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.BLUE)
|
|
2797
|
+
c_code = self._read_source_span(rec)
|
|
2798
|
+
typer.secho(f"[c2rust-transpiler][read] 已加载 {len(c_code.splitlines()) if c_code else 0} 行", fg=typer.colors.BLUE)
|
|
2799
|
+
|
|
2800
|
+
# 若缺少源码片段且缺乏签名/参数信息,则跳过本函数,记录进度以便后续处理
|
|
2801
|
+
if not c_code and not (getattr(rec, "signature", "") or getattr(rec, "params", None)):
|
|
2802
|
+
skipped = self.progress.get("skipped_missing_source") or []
|
|
2803
|
+
if rec.id not in skipped:
|
|
2804
|
+
skipped.append(rec.id)
|
|
2805
|
+
self.progress["skipped_missing_source"] = skipped
|
|
2806
|
+
typer.secho(f"[c2rust-transpiler] {progress_info} 跳过:缺少源码与签名信息 -> {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.YELLOW)
|
|
2807
|
+
self._save_progress()
|
|
2808
|
+
continue
|
|
2809
|
+
# 1) 规划:模块路径与Rust签名
|
|
2810
|
+
typer.secho(f"[c2rust-transpiler][plan] {progress_info} 正在规划模块与签名: {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.CYAN)
|
|
2811
|
+
module, rust_sig, skip_implementation = self._plan_module_and_signature(rec, c_code)
|
|
2812
|
+
typer.secho(f"[c2rust-transpiler][plan] 已选择 模块={module}, 签名={rust_sig}", fg=typer.colors.CYAN)
|
|
2813
|
+
|
|
2814
|
+
# 记录当前进度
|
|
2815
|
+
self._update_progress_current(rec, module, rust_sig)
|
|
2816
|
+
typer.secho(f"[c2rust-transpiler][progress] 已更新当前进度记录 id={rec.id}", fg=typer.colors.CYAN)
|
|
2817
|
+
|
|
2818
|
+
# 如果标记为跳过实现,则直接标记为已转换
|
|
2819
|
+
if skip_implementation:
|
|
2820
|
+
typer.secho(f"[c2rust-transpiler][skip-impl] 函数 {rec.qname or rec.name} 评估为不需要实现,跳过实现阶段", fg=typer.colors.CYAN)
|
|
2821
|
+
# 直接标记为已转换,跳过代码生成、构建和审查阶段
|
|
2822
|
+
self._mark_converted(rec, module, rust_sig)
|
|
2823
|
+
typer.secho(f"[c2rust-transpiler][mark] 已标记并建立映射: {rec.qname or rec.name} -> {module} (跳过实现,视为已实现)", fg=typer.colors.GREEN)
|
|
2824
|
+
continue
|
|
2246
2825
|
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
typer.secho(f"[c2rust-transpiler][deps] 未解析的被调符号: {', '.join(unresolved) if unresolved else '(none)'}", fg=typer.colors.BLUE)
|
|
2250
|
-
typer.secho(f"[c2rust-transpiler][gen] 正在为 {rec.qname or rec.name} 生成 Rust 实现", fg=typer.colors.GREEN)
|
|
2251
|
-
self._codeagent_generate_impl(rec, c_code, module, rust_sig, unresolved)
|
|
2252
|
-
typer.secho(f"[c2rust-transpiler][gen] 已在 {module} 生成或更新实现", fg=typer.colors.GREEN)
|
|
2253
|
-
# 刷新精简上下文(防止签名/模块调整后提示不同步)
|
|
2254
|
-
try:
|
|
2255
|
-
self._refresh_compact_context(rec, module, rust_sig)
|
|
2256
|
-
except Exception:
|
|
2257
|
-
pass
|
|
2826
|
+
# 初始化函数上下文与代码编写与修复Agent复用缓存(只在当前函数开始时执行一次)
|
|
2827
|
+
self._reset_function_context(rec, module, rust_sig, c_code)
|
|
2258
2828
|
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2829
|
+
# 1.5) 确保模块声明链(提前到生成实现之前,避免生成的代码无法被正确引用)
|
|
2830
|
+
try:
|
|
2831
|
+
self._ensure_mod_chain_for_module(module)
|
|
2832
|
+
typer.secho(f"[c2rust-transpiler][mod] 已补齐 {module} 的 mod.rs 声明链", fg=typer.colors.GREEN)
|
|
2833
|
+
# 确保顶层模块在 src/lib.rs 中被公开
|
|
2834
|
+
mp = Path(module)
|
|
2835
|
+
crate_root = self.crate_dir.resolve()
|
|
2836
|
+
rel = mp.resolve().relative_to(crate_root) if mp.is_absolute() else Path(module)
|
|
2837
|
+
rel_s = str(rel).replace("\\", "/")
|
|
2838
|
+
if rel_s.startswith("./"):
|
|
2839
|
+
rel_s = rel_s[2:]
|
|
2840
|
+
if rel_s.startswith("src/"):
|
|
2841
|
+
parts = rel_s[len("src/"):].strip("/").split("/")
|
|
2842
|
+
if parts and parts[0]:
|
|
2843
|
+
top_mod = parts[0]
|
|
2844
|
+
# 过滤掉 "mod" 关键字和 .rs 文件
|
|
2845
|
+
if top_mod != "mod" and not top_mod.endswith(".rs"):
|
|
2846
|
+
self._ensure_top_level_pub_mod(top_mod)
|
|
2847
|
+
typer.secho(f"[c2rust-transpiler][mod] 已在 src/lib.rs 确保顶层 pub mod {top_mod}", fg=typer.colors.GREEN)
|
|
2848
|
+
cur = self.progress.get("current") or {}
|
|
2849
|
+
cur["mod_chain_fixed"] = True
|
|
2850
|
+
cur["mod_visibility_fixed"] = True
|
|
2851
|
+
self.progress["current"] = cur
|
|
2852
|
+
self._save_progress()
|
|
2853
|
+
except Exception:
|
|
2854
|
+
pass
|
|
2267
2855
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2856
|
+
# 在处理函数前,记录当前的 commit id(用于失败回退)
|
|
2857
|
+
self._current_function_start_commit = self._get_crate_commit_hash()
|
|
2858
|
+
if self._current_function_start_commit:
|
|
2859
|
+
typer.secho(f"[c2rust-transpiler][commit] 记录函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.BLUE)
|
|
2860
|
+
else:
|
|
2861
|
+
typer.secho("[c2rust-transpiler][commit] 警告:无法获取 commit id,将无法在失败时回退", fg=typer.colors.YELLOW)
|
|
2862
|
+
|
|
2863
|
+
# 重置连续失败计数(每个新函数开始时重置)
|
|
2864
|
+
self._consecutive_fix_failures = 0
|
|
2865
|
+
|
|
2866
|
+
# 使用循环来处理函数,支持失败回退后重新开始
|
|
2867
|
+
function_retry_count = 0
|
|
2868
|
+
max_function_retries = MAX_FUNCTION_RETRIES
|
|
2869
|
+
while function_retry_count <= max_function_retries:
|
|
2870
|
+
if function_retry_count > 0:
|
|
2871
|
+
typer.secho(f"[c2rust-transpiler][retry] 重新开始处理函数 (第 {function_retry_count} 次重试)", fg=typer.colors.YELLOW)
|
|
2872
|
+
# 重新记录 commit id(回退后的新 commit)
|
|
2873
|
+
self._current_function_start_commit = self._get_crate_commit_hash()
|
|
2874
|
+
if self._current_function_start_commit:
|
|
2875
|
+
typer.secho(f"[c2rust-transpiler][commit] 重新记录函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.BLUE)
|
|
2876
|
+
# 重置连续失败计数(重新开始时重置)
|
|
2877
|
+
self._consecutive_fix_failures = 0
|
|
2878
|
+
|
|
2879
|
+
# 2) 生成实现
|
|
2880
|
+
unresolved = self._untranslated_callee_symbols(rec)
|
|
2881
|
+
typer.secho(f"[c2rust-transpiler][deps] {progress_info} 未解析的被调符号: {', '.join(unresolved) if unresolved else '(none)'}", fg=typer.colors.BLUE)
|
|
2882
|
+
typer.secho(f"[c2rust-transpiler][gen] {progress_info} 正在为 {rec.qname or rec.name} 生成 Rust 实现", fg=typer.colors.GREEN)
|
|
2883
|
+
self._codeagent_generate_impl(rec, c_code, module, rust_sig, unresolved)
|
|
2884
|
+
typer.secho(f"[c2rust-transpiler][gen] 已在 {module} 生成或更新实现", fg=typer.colors.GREEN)
|
|
2885
|
+
# 刷新精简上下文(防止签名/模块调整后提示不同步)
|
|
2886
|
+
try:
|
|
2887
|
+
self._refresh_compact_context(rec, module, rust_sig)
|
|
2888
|
+
except Exception:
|
|
2889
|
+
pass
|
|
2272
2890
|
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2891
|
+
# 3) 构建与修复
|
|
2892
|
+
typer.secho("[c2rust-transpiler][build] 开始 cargo 测试循环", fg=typer.colors.MAGENTA)
|
|
2893
|
+
ok = self._cargo_build_loop()
|
|
2894
|
+
|
|
2895
|
+
# 检查是否需要重新开始(回退后)
|
|
2896
|
+
if ok is None:
|
|
2897
|
+
# 需要重新开始
|
|
2898
|
+
function_retry_count += 1
|
|
2899
|
+
if function_retry_count > max_function_retries:
|
|
2900
|
+
typer.secho(f"[c2rust-transpiler] 函数重新开始次数已达上限({max_function_retries}),停止处理该函数", fg=typer.colors.RED)
|
|
2901
|
+
# 保留当前状态,便于下次 resume
|
|
2902
|
+
return
|
|
2903
|
+
# 重置连续失败计数
|
|
2904
|
+
self._consecutive_fix_failures = 0
|
|
2905
|
+
# 继续循环,重新开始处理
|
|
2906
|
+
continue
|
|
2907
|
+
|
|
2908
|
+
typer.secho(f"[c2rust-transpiler][build] 构建结果: {'通过' if ok else '失败'}", fg=typer.colors.MAGENTA)
|
|
2909
|
+
if not ok:
|
|
2910
|
+
typer.secho("[c2rust-transpiler] 在重试次数限制内未能成功构建,已停止。", fg=typer.colors.RED)
|
|
2911
|
+
# 保留当前状态,便于下次 resume
|
|
2912
|
+
return
|
|
2913
|
+
|
|
2914
|
+
# 构建成功,跳出循环继续后续流程
|
|
2915
|
+
break
|
|
2276
2916
|
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2917
|
+
# 4) 审查与优化(复用 Review Agent)
|
|
2918
|
+
typer.secho(f"[c2rust-transpiler][review] {progress_info} 开始代码审查: {rec.qname or rec.name}", fg=typer.colors.MAGENTA)
|
|
2919
|
+
self._review_and_optimize(rec, module, rust_sig)
|
|
2920
|
+
typer.secho("[c2rust-transpiler][review] 代码审查完成", fg=typer.colors.MAGENTA)
|
|
2921
|
+
|
|
2922
|
+
# 5) 标记已转换与映射记录(JSONL)
|
|
2923
|
+
self._mark_converted(rec, module, rust_sig)
|
|
2924
|
+
typer.secho(f"[c2rust-transpiler][mark] {progress_info} 已标记并建立映射: {rec.qname or rec.name} -> {module}", fg=typer.colors.GREEN)
|
|
2925
|
+
|
|
2926
|
+
# 6) 若此前有其它函数因依赖当前符号而在源码中放置了 todo!("<symbol>"),则立即回头消除(复用代码编写与修复Agent)
|
|
2927
|
+
current_rust_fn = self._extract_rust_fn_name_from_sig(rust_sig)
|
|
2928
|
+
# 收集需要处理的符号(去重,避免 qname 和 name 相同时重复处理)
|
|
2929
|
+
symbols_to_resolve = []
|
|
2930
|
+
if rec.qname:
|
|
2931
|
+
symbols_to_resolve.append(rec.qname)
|
|
2932
|
+
if rec.name and rec.name != rec.qname: # 如果 name 与 qname 不同,才添加
|
|
2933
|
+
symbols_to_resolve.append(rec.name)
|
|
2934
|
+
# 处理每个符号(去重后)
|
|
2935
|
+
for sym in symbols_to_resolve:
|
|
2282
2936
|
typer.secho(f"[c2rust-transpiler][todo] 清理 todo!(\'{sym}\') 的出现位置", fg=typer.colors.BLUE)
|
|
2283
2937
|
self._resolve_pending_todos_for_symbol(sym, module, current_rust_fn, rust_sig)
|
|
2938
|
+
# 如果有处理任何符号,统一运行一次 cargo test(避免重复运行)
|
|
2939
|
+
if symbols_to_resolve:
|
|
2284
2940
|
typer.secho("[c2rust-transpiler][build] 处理 todo 后重新运行 cargo test", fg=typer.colors.MAGENTA)
|
|
2285
2941
|
self._cargo_build_loop()
|
|
2286
2942
|
|
|
2287
|
-
|
|
2943
|
+
typer.secho("[c2rust-transpiler] 所有符合条件的函数均已处理完毕。", fg=typer.colors.GREEN)
|
|
2944
|
+
finally:
|
|
2945
|
+
os.chdir(prev_cwd)
|
|
2946
|
+
typer.secho(f"[c2rust-transpiler][end] 已恢复工作目录: {os.getcwd()}", fg=typer.colors.BLUE)
|
|
2288
2947
|
|
|
2289
2948
|
|
|
2290
2949
|
def run_transpile(
|
|
2291
2950
|
project_root: Union[str, Path] = ".",
|
|
2292
2951
|
crate_dir: Optional[Union[str, Path]] = None,
|
|
2293
2952
|
llm_group: Optional[str] = None,
|
|
2294
|
-
plan_max_retries: int =
|
|
2953
|
+
plan_max_retries: int = DEFAULT_PLAN_MAX_RETRIES_ENTRY,
|
|
2295
2954
|
max_retries: int = 0, # 兼容旧接口
|
|
2296
2955
|
check_max_retries: Optional[int] = None,
|
|
2297
2956
|
test_max_retries: Optional[int] = None,
|
|
2298
2957
|
review_max_iterations: int = DEFAULT_REVIEW_MAX_ITERATIONS,
|
|
2299
|
-
|
|
2300
|
-
|
|
2958
|
+
disabled_libraries: Optional[List[str]] = None, # None 表示从配置文件恢复
|
|
2959
|
+
root_symbols: Optional[List[str]] = None, # None 表示从配置文件恢复
|
|
2301
2960
|
non_interactive: bool = True,
|
|
2302
2961
|
) -> None:
|
|
2303
2962
|
"""
|
|
@@ -2306,8 +2965,7 @@ def run_transpile(
|
|
|
2306
2965
|
- crate_dir: Rust crate 根目录;默认遵循 "<parent>/<cwd_name>_rs"(与当前目录同级,若 project_root 为 ".")
|
|
2307
2966
|
- llm_group: 指定 LLM 模型组
|
|
2308
2967
|
- max_retries: 构建与审查迭代的最大次数
|
|
2309
|
-
|
|
2310
|
-
- only: 仅转译给定列表中的函数(函数名或限定名)
|
|
2968
|
+
注意: 断点续跑功能默认始终启用
|
|
2311
2969
|
"""
|
|
2312
2970
|
t = Transpiler(
|
|
2313
2971
|
project_root=project_root,
|
|
@@ -2318,8 +2976,8 @@ def run_transpile(
|
|
|
2318
2976
|
check_max_retries=check_max_retries,
|
|
2319
2977
|
test_max_retries=test_max_retries,
|
|
2320
2978
|
review_max_iterations=review_max_iterations,
|
|
2321
|
-
|
|
2322
|
-
|
|
2979
|
+
disabled_libraries=disabled_libraries,
|
|
2980
|
+
root_symbols=root_symbols,
|
|
2323
2981
|
non_interactive=non_interactive,
|
|
2324
2982
|
)
|
|
2325
2983
|
t.transpile()
|