通过编译数据库和Python脚本,让AI仅做提议、编译器做判定,实现C++头文件依赖图的自动精简清理,解决了模型建议与实际构建结果不一致的CI问题。
核心结论:免费模型可以给出看似合理的 C++ #include 删除建议,但真正有价值的产物不是建议本身——而是针对每个候选的干净构建门控,让每一次删除都要证明自己没有隐性依赖传递include。
示例项目是一个跨平台 C++ 服务,包含 41 个翻译单元,混合了标准库头文件和内部头文件,CI 偶尔会在"移除未使用include"的提交上翻车——本地构建正常,却在干净 runner 上失败。
声明:本文是 MonkeyCode 产品推广的一部分。工作流使用提供商的免费模型访问权限来生成候选,使用免费服务器选项作为干净室构建目标。
保持 include 图干净,同时不把每个模型建议都变成合并请求。每条拟删除的候选必须满足:
适用于唯一一组源文件/头文件配对。
在干净 runner 上构建成功。
恢复后不破坏下一次本地构建。
该门控使用编译数据库和一个小型 Python 编排器。模型只负责提议,编译器说了算。
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug
优先使用结构化 JSON 而非 diff。候选指一个文件加一条待删除的 header。这样解析简单,也防止模型把不相关的改动混在一起。
提示词要求结构化输出,遵循三条规则:
每个文件返回一个 JSON 对象。
不要猜测已处于条件编译状态的 header 的删除。
不要移除只提供传递符号的 include。
编排器先备份文件,删除指定的 include 行,运行干净构建,再恢复原文件。
import json
import subprocess
from pathlib import Path
def candidate_key(line, header):
return line.strip() == f'#include {header}'
def gate(proposals, build_dir='build'):
report = []
for item in proposals:
file = Path(item['file'])
header = item['remove']
original = file.read_text()
if not any(candidate_key(line, header) for line in original.splitlines()):
report.append({'file': str(file), 'include': header, 'status': 'not_found'})
continue
modified = [line for line in original.splitlines() if not candidate_key(line, header)]
file.write_text(chr(10).join(modified) + chr(10))
run = subprocess.run(
['cmake', '--build', build_dir, '--target', 'all', '-j', '2'],
capture_output=True,
text=True,
)
file.write_text(original)
report.append({
'file': str(file),
'include': header,
'status': 'accepted' if run.returncode == 0 else 'rejected',
'tail': run.stderr[-400:] if run.returncode != 0 else '',
})
return report
if __name__ == '__main__':
proposals = json.loads(Path('proposals.json').read_text())
print(json.dumps(gate(proposals), indent=2))
将同样的门控移入 CI 或全新容器。免费服务器选项剔除了本地产物、ccache 和陈旧的构建目录。如果干净构建失败,候选即被拒绝,即使本地构建通过也不行。
以下报告格式才是真正的审查信号。这不是基准测试——你项目的失败类别会有所不同。
accepted 行是安全候选。rejected 行说明了为什么干净构建必须是最底线;clang-cl 失败在开发者机器上不可见,但在干净 runner 上暴露了。
模型输出倾向于把很多 include 改动打包在一个 diff 里。一次糟糕的删除可能藏在同一次提交中另一次好的删除背后。按候选逐一做门控从机制上把它们分开。这也给你提供了审查产物:一份 JSON 报告,包含精确的失败尾部信息。
构建门控能捕获编译时破坏;但捕获不了 ODR 违规、运行时 ABI 问题,或符号解析带来的语义变化。
单一平台干净构建不是完整的平台矩阵。
门控只移除精确的 include 行;条件 include 和重度预处理 header 需要专门的解析器。
免费服务器选项可能有可用性限制,本文未涉及;依赖它做生产 CI 前请核查当前条款。
本工作流不验证模型的推理过程,只验证候选能否编译。
如果你的代码库是大型 monorepo,每条候选后都重建太慢,不适用。如果无法将文件路径、文件内容或构建诊断信息发送给外部模型,不适用。如果用于安全关键或需形式化验证的代码,仅通过构建还不够作为接受标准,不适用。
免费模型访问降低了提出清理补丁的成本,但无法降低证明它们正确的成本。一个小的、按候选逐一执行的 C++ 门控,加上免费服务器构建,是务实的折中:模型提议,编译器决定,每条删除都要挣得合并的资格。
如果你已有免费模型和免费服务器访问,在开第一个清理合并请求之前先把门控接好。