jarvis-ai-assistant 0.7.8__py3-none-any.whl → 1.0.2__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 +567 -222
- jarvis/jarvis_agent/agent_manager.py +19 -12
- jarvis/jarvis_agent/builtin_input_handler.py +79 -11
- jarvis/jarvis_agent/config_editor.py +7 -2
- jarvis/jarvis_agent/event_bus.py +24 -13
- jarvis/jarvis_agent/events.py +19 -1
- jarvis/jarvis_agent/file_context_handler.py +67 -64
- jarvis/jarvis_agent/file_methodology_manager.py +38 -24
- jarvis/jarvis_agent/jarvis.py +186 -114
- jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
- jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
- jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
- jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
- jarvis/jarvis_agent/language_support_info.py +250 -219
- jarvis/jarvis_agent/main.py +19 -23
- jarvis/jarvis_agent/memory_manager.py +9 -6
- jarvis/jarvis_agent/methodology_share_manager.py +21 -15
- jarvis/jarvis_agent/output_handler.py +4 -2
- jarvis/jarvis_agent/prompt_builder.py +7 -6
- jarvis/jarvis_agent/prompt_manager.py +113 -8
- jarvis/jarvis_agent/prompts.py +317 -85
- jarvis/jarvis_agent/protocols.py +5 -2
- jarvis/jarvis_agent/run_loop.py +192 -32
- jarvis/jarvis_agent/session_manager.py +7 -3
- jarvis/jarvis_agent/share_manager.py +23 -13
- jarvis/jarvis_agent/shell_input_handler.py +12 -8
- jarvis/jarvis_agent/stdio_redirect.py +25 -26
- jarvis/jarvis_agent/task_analyzer.py +29 -23
- jarvis/jarvis_agent/task_list.py +869 -0
- jarvis/jarvis_agent/task_manager.py +26 -23
- jarvis/jarvis_agent/tool_executor.py +6 -5
- jarvis/jarvis_agent/tool_share_manager.py +24 -14
- jarvis/jarvis_agent/user_interaction.py +3 -3
- jarvis/jarvis_agent/utils.py +9 -1
- jarvis/jarvis_agent/web_bridge.py +37 -17
- jarvis/jarvis_agent/web_output_sink.py +5 -2
- jarvis/jarvis_agent/web_server.py +165 -36
- jarvis/jarvis_c2rust/__init__.py +1 -1
- jarvis/jarvis_c2rust/cli.py +260 -141
- jarvis/jarvis_c2rust/collector.py +37 -18
- jarvis/jarvis_c2rust/constants.py +60 -0
- jarvis/jarvis_c2rust/library_replacer.py +242 -1010
- jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
- jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
- jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
- jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
- jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
- jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
- jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
- jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
- jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
- jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
- jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
- jarvis/jarvis_c2rust/loaders.py +28 -10
- jarvis/jarvis_c2rust/models.py +5 -2
- jarvis/jarvis_c2rust/optimizer.py +192 -1974
- jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
- jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
- jarvis/jarvis_c2rust/optimizer_config.py +49 -0
- jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
- jarvis/jarvis_c2rust/optimizer_options.py +48 -0
- jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
- jarvis/jarvis_c2rust/optimizer_report.py +52 -0
- jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
- jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
- jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
- jarvis/jarvis_c2rust/scanner.py +229 -166
- jarvis/jarvis_c2rust/transpiler.py +531 -2732
- jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
- jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
- jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
- jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
- jarvis/jarvis_c2rust/transpiler_config.py +178 -0
- jarvis/jarvis_c2rust/transpiler_context.py +122 -0
- jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
- jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
- jarvis/jarvis_c2rust/transpiler_git.py +163 -0
- jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
- jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
- jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
- jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
- jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
- jarvis/jarvis_c2rust/utils.py +269 -79
- jarvis/jarvis_code_agent/after_change.py +233 -0
- jarvis/jarvis_code_agent/build_validation_config.py +37 -30
- jarvis/jarvis_code_agent/builtin_rules.py +68 -0
- jarvis/jarvis_code_agent/code_agent.py +976 -1517
- jarvis/jarvis_code_agent/code_agent_build.py +227 -0
- jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
- jarvis/jarvis_code_agent/code_agent_git.py +525 -0
- jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
- jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
- jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
- jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
- jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
- jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
- jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
- jarvis/jarvis_code_agent/lint.py +223 -524
- jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
- jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
- jarvis/jarvis_code_agent/rules/code_review.md +115 -0
- jarvis/jarvis_code_agent/rules/documentation.md +165 -0
- jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
- jarvis/jarvis_code_agent/rules/performance.md +158 -0
- jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
- jarvis/jarvis_code_agent/rules/security.md +160 -0
- jarvis/jarvis_code_agent/rules/tdd.md +78 -0
- jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
- jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
- jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
- jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
- jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
- jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
- jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
- jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
- jarvis/jarvis_code_agent/utils.py +36 -26
- jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
- jarvis/jarvis_code_analysis/code_review.py +64 -33
- jarvis/jarvis_data/config_schema.json +285 -192
- jarvis/jarvis_git_squash/main.py +8 -6
- jarvis/jarvis_git_utils/git_commiter.py +53 -76
- jarvis/jarvis_mcp/__init__.py +5 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
- jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
- jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
- jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
- jarvis/jarvis_methodology/main.py +48 -39
- jarvis/jarvis_multi_agent/__init__.py +56 -23
- jarvis/jarvis_multi_agent/main.py +15 -18
- jarvis/jarvis_platform/base.py +179 -111
- jarvis/jarvis_platform/human.py +27 -16
- jarvis/jarvis_platform/kimi.py +52 -45
- jarvis/jarvis_platform/openai.py +101 -40
- jarvis/jarvis_platform/registry.py +51 -33
- jarvis/jarvis_platform/tongyi.py +68 -38
- jarvis/jarvis_platform/yuanbao.py +59 -43
- jarvis/jarvis_platform_manager/main.py +68 -76
- jarvis/jarvis_platform_manager/service.py +24 -14
- jarvis/jarvis_rag/README_CONFIG.md +314 -0
- jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
- jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
- jarvis/jarvis_rag/__init__.py +57 -4
- jarvis/jarvis_rag/cache.py +3 -1
- jarvis/jarvis_rag/cli.py +48 -68
- jarvis/jarvis_rag/embedding_interface.py +39 -0
- jarvis/jarvis_rag/embedding_manager.py +7 -230
- jarvis/jarvis_rag/embeddings/__init__.py +41 -0
- jarvis/jarvis_rag/embeddings/base.py +114 -0
- jarvis/jarvis_rag/embeddings/cohere.py +66 -0
- jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
- jarvis/jarvis_rag/embeddings/local.py +260 -0
- jarvis/jarvis_rag/embeddings/openai.py +62 -0
- jarvis/jarvis_rag/embeddings/registry.py +293 -0
- jarvis/jarvis_rag/llm_interface.py +8 -6
- jarvis/jarvis_rag/query_rewriter.py +8 -9
- jarvis/jarvis_rag/rag_pipeline.py +61 -52
- jarvis/jarvis_rag/reranker.py +7 -75
- jarvis/jarvis_rag/reranker_interface.py +32 -0
- jarvis/jarvis_rag/rerankers/__init__.py +41 -0
- jarvis/jarvis_rag/rerankers/base.py +109 -0
- jarvis/jarvis_rag/rerankers/cohere.py +67 -0
- jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
- jarvis/jarvis_rag/rerankers/jina.py +79 -0
- jarvis/jarvis_rag/rerankers/local.py +89 -0
- jarvis/jarvis_rag/rerankers/registry.py +293 -0
- jarvis/jarvis_rag/retriever.py +58 -43
- jarvis/jarvis_sec/__init__.py +66 -141
- jarvis/jarvis_sec/agents.py +21 -17
- jarvis/jarvis_sec/analysis.py +80 -33
- jarvis/jarvis_sec/checkers/__init__.py +7 -13
- jarvis/jarvis_sec/checkers/c_checker.py +356 -164
- jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
- jarvis/jarvis_sec/cli.py +43 -21
- jarvis/jarvis_sec/clustering.py +430 -272
- jarvis/jarvis_sec/file_manager.py +99 -55
- jarvis/jarvis_sec/parsers.py +9 -6
- jarvis/jarvis_sec/prompts.py +4 -3
- jarvis/jarvis_sec/report.py +44 -22
- jarvis/jarvis_sec/review.py +180 -107
- jarvis/jarvis_sec/status.py +50 -41
- jarvis/jarvis_sec/types.py +3 -0
- jarvis/jarvis_sec/utils.py +160 -83
- jarvis/jarvis_sec/verification.py +411 -181
- jarvis/jarvis_sec/workflow.py +132 -21
- jarvis/jarvis_smart_shell/main.py +28 -41
- jarvis/jarvis_stats/cli.py +14 -12
- jarvis/jarvis_stats/stats.py +28 -19
- jarvis/jarvis_stats/storage.py +14 -8
- jarvis/jarvis_stats/visualizer.py +12 -7
- jarvis/jarvis_tools/base.py +5 -2
- jarvis/jarvis_tools/clear_memory.py +13 -9
- jarvis/jarvis_tools/cli/main.py +23 -18
- jarvis/jarvis_tools/edit_file.py +572 -873
- jarvis/jarvis_tools/execute_script.py +10 -7
- jarvis/jarvis_tools/file_analyzer.py +7 -8
- jarvis/jarvis_tools/meta_agent.py +287 -0
- jarvis/jarvis_tools/methodology.py +5 -3
- jarvis/jarvis_tools/read_code.py +305 -1438
- jarvis/jarvis_tools/read_symbols.py +50 -17
- jarvis/jarvis_tools/read_webpage.py +19 -18
- jarvis/jarvis_tools/registry.py +435 -156
- jarvis/jarvis_tools/retrieve_memory.py +16 -11
- jarvis/jarvis_tools/save_memory.py +8 -6
- jarvis/jarvis_tools/search_web.py +31 -31
- jarvis/jarvis_tools/sub_agent.py +32 -28
- jarvis/jarvis_tools/sub_code_agent.py +44 -60
- jarvis/jarvis_tools/task_list_manager.py +1811 -0
- jarvis/jarvis_tools/virtual_tty.py +29 -19
- jarvis/jarvis_utils/__init__.py +4 -0
- jarvis/jarvis_utils/builtin_replace_map.py +2 -1
- jarvis/jarvis_utils/clipboard.py +9 -8
- jarvis/jarvis_utils/collections.py +331 -0
- jarvis/jarvis_utils/config.py +699 -194
- jarvis/jarvis_utils/dialogue_recorder.py +294 -0
- jarvis/jarvis_utils/embedding.py +6 -3
- jarvis/jarvis_utils/file_processors.py +7 -1
- jarvis/jarvis_utils/fzf.py +9 -3
- jarvis/jarvis_utils/git_utils.py +71 -42
- jarvis/jarvis_utils/globals.py +116 -32
- jarvis/jarvis_utils/http.py +6 -2
- jarvis/jarvis_utils/input.py +318 -83
- jarvis/jarvis_utils/jsonnet_compat.py +119 -104
- jarvis/jarvis_utils/methodology.py +37 -28
- jarvis/jarvis_utils/output.py +201 -44
- jarvis/jarvis_utils/utils.py +986 -628
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
- jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
- jarvis/jarvis_tools/generate_new_tool.py +0 -205
- jarvis/jarvis_tools/lsp_client.py +0 -1552
- jarvis/jarvis_tools/rewrite_file.py +0 -105
- jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
代码生成模块
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List
|
|
10
|
+
from typing import cast
|
|
11
|
+
|
|
12
|
+
from jarvis.jarvis_utils.output import PrettyOutput
|
|
13
|
+
|
|
14
|
+
from jarvis.jarvis_c2rust.models import FnRecord
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GenerationManager:
|
|
18
|
+
"""代码生成管理器"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
project_root: Path,
|
|
23
|
+
crate_dir: Path,
|
|
24
|
+
data_dir: Path,
|
|
25
|
+
disabled_libraries: List[str],
|
|
26
|
+
extract_compile_flags_func,
|
|
27
|
+
append_additional_notes_func,
|
|
28
|
+
is_root_symbol_func,
|
|
29
|
+
get_generation_agent_func,
|
|
30
|
+
compose_prompt_with_context_func,
|
|
31
|
+
check_and_handle_test_deletion_func,
|
|
32
|
+
get_crate_commit_hash_func,
|
|
33
|
+
ensure_top_level_pub_mod_func,
|
|
34
|
+
) -> None:
|
|
35
|
+
self.project_root = project_root
|
|
36
|
+
self.crate_dir = crate_dir
|
|
37
|
+
self.data_dir = data_dir
|
|
38
|
+
self.disabled_libraries = disabled_libraries
|
|
39
|
+
self.extract_compile_flags = extract_compile_flags_func
|
|
40
|
+
self.append_additional_notes = append_additional_notes_func
|
|
41
|
+
self.is_root_symbol = is_root_symbol_func
|
|
42
|
+
self.get_generation_agent = get_generation_agent_func
|
|
43
|
+
self.compose_prompt_with_context = compose_prompt_with_context_func
|
|
44
|
+
self.check_and_handle_test_deletion = check_and_handle_test_deletion_func
|
|
45
|
+
self.get_crate_commit_hash = get_crate_commit_hash_func
|
|
46
|
+
self.ensure_top_level_pub_mod = ensure_top_level_pub_mod_func
|
|
47
|
+
|
|
48
|
+
def build_generate_impl_prompt(
|
|
49
|
+
self,
|
|
50
|
+
rec: FnRecord,
|
|
51
|
+
c_code: str,
|
|
52
|
+
module: str,
|
|
53
|
+
rust_sig: str,
|
|
54
|
+
unresolved: List[str],
|
|
55
|
+
) -> str:
|
|
56
|
+
"""
|
|
57
|
+
构建代码生成提示词。
|
|
58
|
+
|
|
59
|
+
返回完整的提示词字符串。
|
|
60
|
+
"""
|
|
61
|
+
symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
|
|
62
|
+
is_root = self.is_root_symbol(rec)
|
|
63
|
+
# 获取 C 源文件位置信息
|
|
64
|
+
c_file_location = ""
|
|
65
|
+
if hasattr(rec, "file") and rec.file:
|
|
66
|
+
if (
|
|
67
|
+
hasattr(rec, "start_line")
|
|
68
|
+
and hasattr(rec, "end_line")
|
|
69
|
+
and rec.start_line
|
|
70
|
+
and rec.end_line
|
|
71
|
+
):
|
|
72
|
+
c_file_location = f"{rec.file}:{rec.start_line}-{rec.end_line}"
|
|
73
|
+
else:
|
|
74
|
+
c_file_location = str(rec.file)
|
|
75
|
+
|
|
76
|
+
requirement_lines = [
|
|
77
|
+
f"目标:在 {module} 中,使用 TDD 方法为 C 函数 {rec.qname or rec.name} 生成 Rust 实现。",
|
|
78
|
+
f"函数签名:{rust_sig}",
|
|
79
|
+
f"crate 目录:{self.crate_dir.resolve()}",
|
|
80
|
+
f"C 工程目录:{self.project_root.resolve()}",
|
|
81
|
+
*([f"C 源文件位置:{c_file_location}"] if c_file_location else []),
|
|
82
|
+
*(
|
|
83
|
+
["根符号要求:必须使用 `pub` 关键字,模块必须在 src/lib.rs 中导出"]
|
|
84
|
+
if is_root
|
|
85
|
+
else []
|
|
86
|
+
),
|
|
87
|
+
"",
|
|
88
|
+
"【TDD 流程】",
|
|
89
|
+
"1. Red:先写测试(#[cfg(test)] mod tests),基于 C 函数行为设计测试用例",
|
|
90
|
+
"2. Green:编写实现使测试通过,确保与 C 语义等价",
|
|
91
|
+
"3. Refactor:优化代码,保持测试通过",
|
|
92
|
+
" - 如果发现现有测试用例有错误,优先修复测试用例而不是删除",
|
|
93
|
+
"",
|
|
94
|
+
"【核心要求】",
|
|
95
|
+
"- 先写测试再写实现,测试必须可编译通过",
|
|
96
|
+
"- ⚠️ 重要:如果发现现有测试用例有错误(如测试逻辑错误、断言不正确、测试用例设计不当等),应该修复测试用例而不是删除它们。只有在测试用例完全重复、过时或确实不需要时才能删除。",
|
|
97
|
+
"- ⚠️ 重要:不要将正式代码写到测试区域。所有正式的函数实现、类型定义、常量等都应该写在 `#[cfg(test)] mod tests { ... }` 块之外。测试代码(测试函数、测试辅助函数等)才应该写在 `#[cfg(test)] mod tests { ... }` 块内部。",
|
|
98
|
+
"- ⚠️ 重要:测试用例必须尽可能完备,因为后续 review 阶段会检测测试用例完备性,避免返工。测试用例应该包括:",
|
|
99
|
+
" * 主要功能路径的测试:覆盖函数的核心功能和预期行为",
|
|
100
|
+
" * 边界情况测试:空输入(空字符串、空数组、空指针等)、极值输入(最大值、最小值、零值等)、边界值(数组边界、字符串长度边界等)、特殊值(负数、NaN、无穷大等,如果适用)",
|
|
101
|
+
" * 错误情况测试:如果 C 实现有错误处理(如返回错误码、设置 errno 等),测试用例应该覆盖这些错误情况。如果 Rust 实现使用 Result<T, E> 或 Option<T> 处理错误,测试用例应该验证错误情况",
|
|
102
|
+
" * 与 C 实现行为一致性:测试用例的预期结果应该与 C 实现的行为一致",
|
|
103
|
+
" * 测试用例质量:测试名称清晰、断言适当、测试逻辑正确,能够真正验证函数的功能",
|
|
104
|
+
" * 注意:如果函数是资源释放类函数(如 fclose、free 等),在 Rust 中通过 RAII 自动管理,测试用例可以非常简单(如仅验证函数可以调用而不崩溃),这是可以接受的",
|
|
105
|
+
"- 禁止使用 todo!/unimplemented!,必须实现完整功能",
|
|
106
|
+
"- 使用 Rust 原生类型(i32/u32、&str/String、&[T]/&mut [T]、Result<T,E>),避免 C 风格类型",
|
|
107
|
+
'- 禁止使用 extern "C",使用标准 Rust 调用约定',
|
|
108
|
+
"- 保持最小变更,避免无关重构",
|
|
109
|
+
"- 注释使用中文,禁止 use ...::* 通配导入",
|
|
110
|
+
"- 资源释放类函数(fclose/free 等)可通过 RAII 自动管理,提供空实现并在文档中说明",
|
|
111
|
+
*(
|
|
112
|
+
[f"- 禁用库:{', '.join(self.disabled_libraries)}"]
|
|
113
|
+
if self.disabled_libraries
|
|
114
|
+
else []
|
|
115
|
+
),
|
|
116
|
+
"",
|
|
117
|
+
"【依赖处理】",
|
|
118
|
+
"- 检查依赖函数是否已实现,未实现的需一并补齐(遵循 TDD:先测试后实现)",
|
|
119
|
+
"- 使用 read_symbols/read_code 获取 C 源码",
|
|
120
|
+
"- 优先处理底层依赖,确保所有测试通过",
|
|
121
|
+
"",
|
|
122
|
+
"【工具】",
|
|
123
|
+
f'- read_symbols: {{"symbols_file": "{symbols_path}", "symbols": [...]}}',
|
|
124
|
+
"- read_code: 读取 C 源码或 Rust 模块",
|
|
125
|
+
"",
|
|
126
|
+
*([f"未转换符号:{', '.join(unresolved)}"] if unresolved else []),
|
|
127
|
+
"",
|
|
128
|
+
"C 源码:",
|
|
129
|
+
"<C_SOURCE>",
|
|
130
|
+
c_code,
|
|
131
|
+
"</C_SOURCE>",
|
|
132
|
+
"",
|
|
133
|
+
"签名参考:",
|
|
134
|
+
json.dumps(
|
|
135
|
+
{
|
|
136
|
+
"signature": getattr(rec, "signature", ""),
|
|
137
|
+
"params": getattr(rec, "params", None),
|
|
138
|
+
},
|
|
139
|
+
ensure_ascii=False,
|
|
140
|
+
indent=2,
|
|
141
|
+
),
|
|
142
|
+
"",
|
|
143
|
+
"仅输出补丁,不要解释。",
|
|
144
|
+
]
|
|
145
|
+
# 若存在库替代上下文,则附加到实现提示中,便于生成器参考(多库组合、参考API、备注等)
|
|
146
|
+
librep_ctx = None
|
|
147
|
+
try:
|
|
148
|
+
librep_ctx = getattr(rec, "lib_replacement", None)
|
|
149
|
+
except Exception:
|
|
150
|
+
librep_ctx = None
|
|
151
|
+
if isinstance(librep_ctx, dict) and librep_ctx:
|
|
152
|
+
requirement_lines.extend(
|
|
153
|
+
[
|
|
154
|
+
"",
|
|
155
|
+
"库替代上下文(若存在):",
|
|
156
|
+
json.dumps(librep_ctx, ensure_ascii=False, indent=2),
|
|
157
|
+
"",
|
|
158
|
+
]
|
|
159
|
+
)
|
|
160
|
+
# 添加编译参数(如果存在)
|
|
161
|
+
compile_flags = None
|
|
162
|
+
if hasattr(rec, "file") and rec.file:
|
|
163
|
+
compile_flags = self.extract_compile_flags(rec.file)
|
|
164
|
+
if compile_flags:
|
|
165
|
+
requirement_lines.extend(
|
|
166
|
+
[
|
|
167
|
+
"",
|
|
168
|
+
"C文件编译参数(来自 compile_commands.json):",
|
|
169
|
+
compile_flags,
|
|
170
|
+
"",
|
|
171
|
+
]
|
|
172
|
+
)
|
|
173
|
+
prompt = "\n".join(requirement_lines)
|
|
174
|
+
return cast(str, self.append_additional_notes(prompt))
|
|
175
|
+
|
|
176
|
+
def extract_rust_fn_name_from_sig(self, rust_sig: str) -> str:
|
|
177
|
+
"""
|
|
178
|
+
从 rust 签名中提取函数名,支持生命周期参数和泛型参数。
|
|
179
|
+
例如: 'pub fn foo(a: i32) -> i32 { ... }' -> 'foo'
|
|
180
|
+
例如: 'pub fn foo<'a>(bzf: &'a mut BzFile) -> Result<&'a [u8], BzError>' -> 'foo'
|
|
181
|
+
"""
|
|
182
|
+
# 支持生命周期参数和泛型参数:fn name<'a, T>(...)
|
|
183
|
+
m = re.search(
|
|
184
|
+
r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(", rust_sig or ""
|
|
185
|
+
)
|
|
186
|
+
return m.group(1) if m else ""
|
|
187
|
+
|
|
188
|
+
def codeagent_generate_impl(
|
|
189
|
+
self,
|
|
190
|
+
rec: FnRecord,
|
|
191
|
+
c_code: str,
|
|
192
|
+
module: str,
|
|
193
|
+
rust_sig: str,
|
|
194
|
+
unresolved: List[str],
|
|
195
|
+
) -> None:
|
|
196
|
+
"""
|
|
197
|
+
使用 CodeAgent 生成/更新目标模块中的函数实现。
|
|
198
|
+
约束:最小变更,生成可编译的占位实现,尽可能保留后续细化空间。
|
|
199
|
+
"""
|
|
200
|
+
# 构建提示词
|
|
201
|
+
prompt = self.build_generate_impl_prompt(
|
|
202
|
+
rec, c_code, module, rust_sig, unresolved
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# 确保目标模块文件存在(提高补丁应用与实现落盘的确定性)
|
|
206
|
+
try:
|
|
207
|
+
mp = Path(module)
|
|
208
|
+
if not mp.is_absolute():
|
|
209
|
+
mp = (self.crate_dir / module).resolve()
|
|
210
|
+
mp.parent.mkdir(parents=True, exist_ok=True)
|
|
211
|
+
if not mp.exists():
|
|
212
|
+
try:
|
|
213
|
+
mp.write_text(
|
|
214
|
+
"// Auto-created by c2rust transpiler\n", encoding="utf-8"
|
|
215
|
+
)
|
|
216
|
+
PrettyOutput.auto_print(
|
|
217
|
+
f"✅ [c2rust-transpiler][gen] auto-created module file: {mp}"
|
|
218
|
+
)
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
225
|
+
# 记录运行前的 commit
|
|
226
|
+
before_commit = self.get_crate_commit_hash()
|
|
227
|
+
# 使用生成 Agent(可以复用)
|
|
228
|
+
agent = self.get_generation_agent()
|
|
229
|
+
agent.run(
|
|
230
|
+
self.compose_prompt_with_context(prompt),
|
|
231
|
+
prefix="[c2rust-transpiler][gen]",
|
|
232
|
+
suffix="",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# 检测并处理测试代码删除
|
|
236
|
+
if self.check_and_handle_test_deletion(before_commit, agent):
|
|
237
|
+
# 如果回退了,需要重新运行 agent
|
|
238
|
+
PrettyOutput.auto_print(
|
|
239
|
+
"⚠️ [c2rust-transpiler][gen] 检测到测试代码删除问题,已回退,重新运行 agent"
|
|
240
|
+
)
|
|
241
|
+
before_commit = self.get_crate_commit_hash()
|
|
242
|
+
# 重试时使用相同的 prompt(已包含 C 源文件位置信息)
|
|
243
|
+
agent.run(
|
|
244
|
+
self.compose_prompt_with_context(prompt),
|
|
245
|
+
prefix="[c2rust-transpiler][gen][retry]",
|
|
246
|
+
suffix="",
|
|
247
|
+
)
|
|
248
|
+
# 再次检测
|
|
249
|
+
if self.check_and_handle_test_deletion(before_commit, agent):
|
|
250
|
+
PrettyOutput.auto_print(
|
|
251
|
+
"❌ [c2rust-transpiler][gen] 再次检测到测试代码删除问题,已回退"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# 如果是根符号,确保其模块在 lib.rs 中被暴露
|
|
255
|
+
if self.is_root_symbol(rec):
|
|
256
|
+
try:
|
|
257
|
+
mp = Path(module)
|
|
258
|
+
crate_root = self.crate_dir.resolve()
|
|
259
|
+
rel = (
|
|
260
|
+
mp.resolve().relative_to(crate_root)
|
|
261
|
+
if mp.is_absolute()
|
|
262
|
+
else Path(module)
|
|
263
|
+
)
|
|
264
|
+
rel_s = str(rel).replace("\\", "/")
|
|
265
|
+
if rel_s.startswith("./"):
|
|
266
|
+
rel_s = rel_s[2:]
|
|
267
|
+
if rel_s.startswith("src/"):
|
|
268
|
+
parts = rel_s[len("src/") :].strip("/").split("/")
|
|
269
|
+
if parts and parts[0]:
|
|
270
|
+
top_mod = parts[0]
|
|
271
|
+
# 过滤掉 "mod" 关键字和 .rs 文件
|
|
272
|
+
if top_mod != "mod" and not top_mod.endswith(".rs"):
|
|
273
|
+
self.ensure_top_level_pub_mod(top_mod)
|
|
274
|
+
PrettyOutput.auto_print(
|
|
275
|
+
f"📋 [c2rust-transpiler][gen] 根符号 {rec.qname or rec.name} 的模块 {top_mod} 已在 lib.rs 中暴露"
|
|
276
|
+
)
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Git 操作模块
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from jarvis.jarvis_utils.git_utils import get_latest_commit_hash
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitManager:
|
|
13
|
+
"""Git 操作管理器"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, crate_dir: str) -> None:
|
|
16
|
+
self.crate_dir = crate_dir
|
|
17
|
+
|
|
18
|
+
def get_crate_commit_hash(self) -> Optional[str]:
|
|
19
|
+
"""获取 crate 目录的当前 commit id"""
|
|
20
|
+
try:
|
|
21
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
22
|
+
commit_hash = get_latest_commit_hash()
|
|
23
|
+
return commit_hash if commit_hash else None
|
|
24
|
+
except Exception:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
def get_git_diff(self, base_commit: Optional[str] = None) -> str:
|
|
28
|
+
"""
|
|
29
|
+
获取 git diff,显示从 base_commit 到当前工作区的变更
|
|
30
|
+
|
|
31
|
+
参数:
|
|
32
|
+
base_commit: 基准 commit hash,如果为 None 则使用 HEAD
|
|
33
|
+
|
|
34
|
+
返回:
|
|
35
|
+
str: git diff 内容,如果获取失败则返回空字符串
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
# 检查是否是 git 仓库
|
|
39
|
+
check_git_result = subprocess.run(
|
|
40
|
+
["git", "rev-parse", "--git-dir"],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
check=False,
|
|
44
|
+
cwd=self.crate_dir,
|
|
45
|
+
)
|
|
46
|
+
if check_git_result.returncode != 0:
|
|
47
|
+
# 不是 git 仓库,无法获取 diff
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
if base_commit:
|
|
51
|
+
# 先检查 base_commit 是否存在
|
|
52
|
+
check_result = subprocess.run(
|
|
53
|
+
["git", "rev-parse", "--verify", base_commit],
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
check=False,
|
|
57
|
+
cwd=self.crate_dir,
|
|
58
|
+
)
|
|
59
|
+
if check_result.returncode != 0:
|
|
60
|
+
# base_commit 不存在,使用 HEAD 作为基准
|
|
61
|
+
base_commit = None
|
|
62
|
+
|
|
63
|
+
# 检查是否有 HEAD
|
|
64
|
+
head_check = subprocess.run(
|
|
65
|
+
["git", "rev-parse", "--verify", "HEAD"],
|
|
66
|
+
capture_output=True,
|
|
67
|
+
text=True,
|
|
68
|
+
check=False,
|
|
69
|
+
cwd=self.crate_dir,
|
|
70
|
+
)
|
|
71
|
+
has_head = head_check.returncode == 0
|
|
72
|
+
|
|
73
|
+
# 临时暂存新增文件以便获取完整的 diff
|
|
74
|
+
subprocess.run(
|
|
75
|
+
["git", "add", "-N", "."],
|
|
76
|
+
check=False,
|
|
77
|
+
stdout=subprocess.DEVNULL,
|
|
78
|
+
stderr=subprocess.DEVNULL,
|
|
79
|
+
cwd=self.crate_dir,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
if base_commit:
|
|
84
|
+
# 获取从 base_commit 到当前工作区的差异
|
|
85
|
+
result = subprocess.run(
|
|
86
|
+
["git", "diff", base_commit],
|
|
87
|
+
capture_output=True,
|
|
88
|
+
text=True,
|
|
89
|
+
encoding="utf-8",
|
|
90
|
+
errors="replace",
|
|
91
|
+
check=False,
|
|
92
|
+
cwd=self.crate_dir,
|
|
93
|
+
)
|
|
94
|
+
elif has_head:
|
|
95
|
+
# 获取从 HEAD 到当前工作区的差异
|
|
96
|
+
result = subprocess.run(
|
|
97
|
+
["git", "diff", "HEAD"],
|
|
98
|
+
capture_output=True,
|
|
99
|
+
text=True,
|
|
100
|
+
encoding="utf-8",
|
|
101
|
+
errors="replace",
|
|
102
|
+
check=False,
|
|
103
|
+
cwd=self.crate_dir,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
# 空仓库,获取工作区差异
|
|
107
|
+
result = subprocess.run(
|
|
108
|
+
["git", "diff"],
|
|
109
|
+
capture_output=True,
|
|
110
|
+
text=True,
|
|
111
|
+
encoding="utf-8",
|
|
112
|
+
errors="replace",
|
|
113
|
+
check=False,
|
|
114
|
+
cwd=self.crate_dir,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return result.stdout or "" if result.returncode == 0 else ""
|
|
118
|
+
finally:
|
|
119
|
+
# 重置暂存区
|
|
120
|
+
subprocess.run(
|
|
121
|
+
["git", "reset"],
|
|
122
|
+
check=False,
|
|
123
|
+
stdout=subprocess.DEVNULL,
|
|
124
|
+
stderr=subprocess.DEVNULL,
|
|
125
|
+
cwd=self.crate_dir,
|
|
126
|
+
)
|
|
127
|
+
except Exception:
|
|
128
|
+
return ""
|
|
129
|
+
|
|
130
|
+
def reset_to_commit(self, commit_hash: str) -> bool:
|
|
131
|
+
"""回退 crate 目录到指定的 commit"""
|
|
132
|
+
try:
|
|
133
|
+
# 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
|
|
134
|
+
# 检查是否是 git 仓库
|
|
135
|
+
result = subprocess.run(
|
|
136
|
+
["git", "rev-parse", "--git-dir"],
|
|
137
|
+
capture_output=True,
|
|
138
|
+
text=True,
|
|
139
|
+
check=False,
|
|
140
|
+
)
|
|
141
|
+
if result.returncode != 0:
|
|
142
|
+
# 不是 git 仓库,无法回退
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
# 执行硬重置
|
|
146
|
+
result = subprocess.run(
|
|
147
|
+
["git", "reset", "--hard", commit_hash],
|
|
148
|
+
capture_output=True,
|
|
149
|
+
text=True,
|
|
150
|
+
check=False,
|
|
151
|
+
)
|
|
152
|
+
if result.returncode == 0:
|
|
153
|
+
# 清理未跟踪的文件
|
|
154
|
+
subprocess.run(
|
|
155
|
+
["git", "clean", "-fd"],
|
|
156
|
+
capture_output=True,
|
|
157
|
+
text=True,
|
|
158
|
+
check=False,
|
|
159
|
+
)
|
|
160
|
+
return True
|
|
161
|
+
return False
|
|
162
|
+
except Exception:
|
|
163
|
+
return False
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from jarvis.jarvis_utils.output import PrettyOutput
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def ensure_top_level_pub_mod(crate_dir: Path, mod_name: str) -> None:
|
|
10
|
+
"""
|
|
11
|
+
在 src/lib.rs 中确保存在 `pub mod <mod_name>;`
|
|
12
|
+
- 如已存在 `pub mod`,不做改动
|
|
13
|
+
- 如存在 `mod <mod_name>;`,升级为 `pub mod <mod_name>;`
|
|
14
|
+
- 如都不存在,则在文件末尾追加一行 `pub mod <mod_name>;`
|
|
15
|
+
- 最小改动,不覆盖其他内容
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
if not mod_name or mod_name in ("lib", "main", "mod"):
|
|
19
|
+
return
|
|
20
|
+
lib_rs = (crate_dir / "src" / "lib.rs").resolve()
|
|
21
|
+
lib_rs.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
if not lib_rs.exists():
|
|
23
|
+
try:
|
|
24
|
+
lib_rs.write_text(
|
|
25
|
+
"// Auto-generated by c2rust transpiler\n", encoding="utf-8"
|
|
26
|
+
)
|
|
27
|
+
PrettyOutput.auto_print(
|
|
28
|
+
f"✅ [c2rust-transpiler][mod] 已创建 src/lib.rs: {lib_rs}"
|
|
29
|
+
)
|
|
30
|
+
except Exception:
|
|
31
|
+
return
|
|
32
|
+
txt = lib_rs.read_text(encoding="utf-8", errors="replace")
|
|
33
|
+
pub_pat = re.compile(rf"(?m)^\s*pub\s+mod\s+{re.escape(mod_name)}\s*;\s*$")
|
|
34
|
+
mod_pat = re.compile(rf"(?m)^\s*mod\s+{re.escape(mod_name)}\s*;\s*$")
|
|
35
|
+
if pub_pat.search(txt):
|
|
36
|
+
return
|
|
37
|
+
if mod_pat.search(txt):
|
|
38
|
+
# 升级为 pub mod(保留原缩进)
|
|
39
|
+
def _repl(m):
|
|
40
|
+
line = m.group(0)
|
|
41
|
+
match = re.match(r"^(\s*)", line)
|
|
42
|
+
ws = match.group(1) if match is not None else ""
|
|
43
|
+
return f"{ws}pub mod {mod_name};"
|
|
44
|
+
|
|
45
|
+
new_txt = mod_pat.sub(_repl, txt, count=1)
|
|
46
|
+
else:
|
|
47
|
+
new_txt = txt.rstrip() + f"\npub mod {mod_name};\n"
|
|
48
|
+
lib_rs.write_text(new_txt, encoding="utf-8")
|
|
49
|
+
PrettyOutput.auto_print(
|
|
50
|
+
f"✅ [c2rust-transpiler][mod] updated src/lib.rs: ensured pub mod {mod_name}"
|
|
51
|
+
)
|
|
52
|
+
except Exception:
|
|
53
|
+
# 保持稳健,失败不阻塞主流程
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def ensure_mod_rs_decl(dir_path: Path, child_mod: str) -> None:
|
|
58
|
+
"""
|
|
59
|
+
在 dir_path/mod.rs 中确保存在 `pub mod <child_mod>;`
|
|
60
|
+
- 如存在 `mod <child_mod>;` 则升级为 `pub mod <child_mod>;`
|
|
61
|
+
- 如均不存在则在文件末尾追加 `pub mod <child_mod>;`
|
|
62
|
+
- 最小改动,不覆盖其他内容
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
if not child_mod or child_mod in ("lib", "main", "mod"):
|
|
66
|
+
return
|
|
67
|
+
mod_rs = (dir_path / "mod.rs").resolve()
|
|
68
|
+
mod_rs.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
if not mod_rs.exists():
|
|
70
|
+
try:
|
|
71
|
+
mod_rs.write_text(
|
|
72
|
+
"// Auto-generated by c2rust transpiler\n", encoding="utf-8"
|
|
73
|
+
)
|
|
74
|
+
PrettyOutput.auto_print(f"✅ [c2rust-transpiler][mod] 已创建 {mod_rs}")
|
|
75
|
+
except Exception:
|
|
76
|
+
return
|
|
77
|
+
txt = mod_rs.read_text(encoding="utf-8", errors="replace")
|
|
78
|
+
pub_pat = re.compile(rf"(?m)^\s*pub\s+mod\s+{re.escape(child_mod)}\s*;\s*$")
|
|
79
|
+
mod_pat = re.compile(rf"(?m)^\s*mod\s+{re.escape(child_mod)}\s*;\s*$")
|
|
80
|
+
if pub_pat.search(txt):
|
|
81
|
+
return
|
|
82
|
+
if mod_pat.search(txt):
|
|
83
|
+
# 升级为 pub mod(保留原缩进)
|
|
84
|
+
def _repl(m):
|
|
85
|
+
line = m.group(0)
|
|
86
|
+
match = re.match(r"^(\s*)", line)
|
|
87
|
+
ws = match.group(1) if match is not None else ""
|
|
88
|
+
return f"{ws}pub mod {child_mod};"
|
|
89
|
+
|
|
90
|
+
new_txt = mod_pat.sub(_repl, txt, count=1)
|
|
91
|
+
else:
|
|
92
|
+
new_txt = txt.rstrip() + f"\npub mod {child_mod};\n"
|
|
93
|
+
mod_rs.write_text(new_txt, encoding="utf-8")
|
|
94
|
+
PrettyOutput.auto_print(
|
|
95
|
+
f"✅ [c2rust-transpiler][mod] updated {mod_rs}: ensured pub mod {child_mod}"
|
|
96
|
+
)
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def ensure_mod_chain_for_module(crate_dir: Path, module: str) -> None:
|
|
102
|
+
"""
|
|
103
|
+
根据目标模块文件,补齐从该文件所在目录向上的 mod.rs 声明链:
|
|
104
|
+
- 对于 src/foo/bar.rs:在 src/foo/mod.rs 确保 `pub mod bar;`
|
|
105
|
+
并在上层 src/mod.rs(不修改)改为在 src/lib.rs 确保 `pub mod foo;`
|
|
106
|
+
- 对于 src/foo/bar/mod.rs:在 src/foo/mod.rs 确保 `pub mod bar;`
|
|
107
|
+
- 对于多级目录,逐级在上层 mod.rs 确保对子目录的 `pub mod <child>;`
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
mp = Path(module)
|
|
111
|
+
base = mp
|
|
112
|
+
if not mp.is_absolute():
|
|
113
|
+
base = (crate_dir / module).resolve()
|
|
114
|
+
crate_root = crate_dir.resolve()
|
|
115
|
+
# 必须在 crate/src 下
|
|
116
|
+
rel = base.relative_to(crate_root)
|
|
117
|
+
rel_s = str(rel).replace("\\", "/")
|
|
118
|
+
if not rel_s.startswith("src/"):
|
|
119
|
+
return
|
|
120
|
+
# 计算起始目录与首个子模块名
|
|
121
|
+
inside = rel_s[len("src/") :].strip("/")
|
|
122
|
+
if not inside:
|
|
123
|
+
return
|
|
124
|
+
parts = [p for p in inside.split("/") if p] # 过滤空字符串
|
|
125
|
+
if parts[-1].endswith(".rs"):
|
|
126
|
+
if parts[-1] in ("lib.rs", "main.rs"):
|
|
127
|
+
return
|
|
128
|
+
child = parts[-1][:-3] # 去掉 .rs
|
|
129
|
+
# 过滤掉 "mod" 关键字
|
|
130
|
+
if child == "mod":
|
|
131
|
+
return
|
|
132
|
+
if len(parts) > 1:
|
|
133
|
+
start_dir = crate_root / "src" / "/".join(parts[:-1])
|
|
134
|
+
else:
|
|
135
|
+
start_dir = crate_root / "src"
|
|
136
|
+
# 确保 start_dir 在 crate/src 下
|
|
137
|
+
try:
|
|
138
|
+
start_dir_rel = start_dir.relative_to(crate_root)
|
|
139
|
+
if not str(start_dir_rel).replace("\\", "/").startswith("src/"):
|
|
140
|
+
return
|
|
141
|
+
except ValueError:
|
|
142
|
+
return
|
|
143
|
+
# 在当前目录的 mod.rs 确保 pub mod <child>
|
|
144
|
+
if start_dir.name != "src":
|
|
145
|
+
ensure_mod_rs_decl(start_dir, child)
|
|
146
|
+
# 向上逐级确保父目录对当前目录的 pub mod 声明
|
|
147
|
+
cur_dir = start_dir
|
|
148
|
+
else:
|
|
149
|
+
# 末尾为目录(mod.rs 情况):确保父目录对该目录 pub mod
|
|
150
|
+
if parts:
|
|
151
|
+
cur_dir = crate_root / "src" / "/".join(parts)
|
|
152
|
+
# 确保 cur_dir 在 crate/src 下
|
|
153
|
+
try:
|
|
154
|
+
cur_dir_rel = cur_dir.relative_to(crate_root)
|
|
155
|
+
if not str(cur_dir_rel).replace("\\", "/").startswith("src/"):
|
|
156
|
+
return
|
|
157
|
+
except ValueError:
|
|
158
|
+
return
|
|
159
|
+
else:
|
|
160
|
+
return
|
|
161
|
+
# 逐级向上到 src 根(不修改 src/mod.rs,顶层由 lib.rs 公开)
|
|
162
|
+
while True:
|
|
163
|
+
parent = cur_dir.parent
|
|
164
|
+
if not parent.exists():
|
|
165
|
+
break
|
|
166
|
+
# 确保不超过 crate 根目录
|
|
167
|
+
try:
|
|
168
|
+
parent.relative_to(crate_root)
|
|
169
|
+
except ValueError:
|
|
170
|
+
# parent 不在 crate_root 下,停止向上遍历
|
|
171
|
+
break
|
|
172
|
+
if parent.name == "src":
|
|
173
|
+
# 顶层由 ensure_top_level_pub_mod 负责
|
|
174
|
+
break
|
|
175
|
+
# 在 parent/mod.rs 确保 pub mod <cur_dir.name>
|
|
176
|
+
if cur_dir.name == "mod":
|
|
177
|
+
cur_dir = parent
|
|
178
|
+
continue
|
|
179
|
+
try:
|
|
180
|
+
parent_rel = parent.relative_to(crate_root)
|
|
181
|
+
if str(parent_rel).replace("\\", "/").startswith("src/"):
|
|
182
|
+
ensure_mod_rs_decl(parent, cur_dir.name)
|
|
183
|
+
except (ValueError, Exception):
|
|
184
|
+
# parent 不在 crate/src 下,跳过
|
|
185
|
+
break
|
|
186
|
+
cur_dir = parent
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def module_file_to_crate_path(crate_dir: Path, module: str) -> str:
|
|
192
|
+
"""
|
|
193
|
+
将模块文件路径转换为 crate 路径前缀:
|
|
194
|
+
- src/lib.rs -> crate
|
|
195
|
+
- src/foo/mod.rs -> crate::foo
|
|
196
|
+
- src/foo/bar.rs -> crate::foo::bar
|
|
197
|
+
支持绝对路径:若 module 为绝对路径且位于 crate 根目录下,会自动转换为相对路径再解析;
|
|
198
|
+
其它(无法解析为 crate/src 下的路径)统一返回 'crate'
|
|
199
|
+
"""
|
|
200
|
+
mod = str(module).strip()
|
|
201
|
+
# 若传入绝对路径且在 crate_dir 下,转换为相对路径以便后续按 src/ 前缀解析
|
|
202
|
+
try:
|
|
203
|
+
mp = Path(mod)
|
|
204
|
+
if mp.is_absolute():
|
|
205
|
+
try:
|
|
206
|
+
rel = mp.resolve().relative_to(crate_dir.resolve())
|
|
207
|
+
mod = str(rel).replace("\\", "/")
|
|
208
|
+
except Exception:
|
|
209
|
+
# 绝对路径不在 crate_dir 下,保持原样
|
|
210
|
+
pass
|
|
211
|
+
except Exception:
|
|
212
|
+
pass
|
|
213
|
+
# 规范化 ./ 前缀
|
|
214
|
+
if mod.startswith("./"):
|
|
215
|
+
mod = mod[2:]
|
|
216
|
+
# 仅处理位于 src/ 下的模块文件
|
|
217
|
+
if not mod.startswith("src/"):
|
|
218
|
+
return "crate"
|
|
219
|
+
p = mod[len("src/") :]
|
|
220
|
+
if p.endswith("mod.rs"):
|
|
221
|
+
p = p[: -len("mod.rs")]
|
|
222
|
+
elif p.endswith(".rs"):
|
|
223
|
+
p = p[: -len(".rs")]
|
|
224
|
+
p = p.strip("/")
|
|
225
|
+
return "crate" if not p else "crate::" + p.replace("/", "::")
|