|
|
"""
|
|
|
Cppcheck 运行和报告生成模块
|
|
|
"""
|
|
|
import subprocess
|
|
|
import shutil
|
|
|
from pathlib import Path
|
|
|
from typing import Optional, List
|
|
|
|
|
|
|
|
|
def find_cppcheck_executable() -> Optional[str]:
|
|
|
"""查找 cppcheck 可执行文件"""
|
|
|
# 首先检查系统 PATH
|
|
|
cppcheck_path = shutil.which("cppcheck")
|
|
|
if cppcheck_path:
|
|
|
return cppcheck_path
|
|
|
|
|
|
# 检查常见安装位置
|
|
|
common_paths = [
|
|
|
"/usr/bin/cppcheck",
|
|
|
"/usr/local/bin/cppcheck",
|
|
|
"/opt/cppcheck/bin/cppcheck",
|
|
|
]
|
|
|
|
|
|
for path in common_paths:
|
|
|
if Path(path).exists():
|
|
|
return path
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
def run_cppcheck(
|
|
|
target: Path,
|
|
|
output_xml: Path,
|
|
|
enable_all: bool = True,
|
|
|
std: Optional[str] = None,
|
|
|
include_dirs: Optional[List[str]] = None,
|
|
|
suppress: Optional[List[str]] = None,
|
|
|
timeout: int = 300,
|
|
|
cppcheck_path: Optional[str] = None
|
|
|
) -> dict:
|
|
|
"""
|
|
|
运行 cppcheck 并生成 XML 报告
|
|
|
|
|
|
Args:
|
|
|
target: 要检查的文件或目录路径
|
|
|
output_xml: 输出的 XML 报告路径
|
|
|
enable_all: 是否启用所有检查
|
|
|
std: C/C++ 标准(如 "c99", "c11", "c++17")
|
|
|
include_dirs: 额外的头文件搜索路径列表
|
|
|
suppress: 要抑制的规则 ID 列表
|
|
|
timeout: 超时时间(秒)
|
|
|
cppcheck_path: cppcheck 可执行文件路径(如果为 None,则自动查找)
|
|
|
|
|
|
Returns:
|
|
|
dict: 包含执行结果的字典
|
|
|
{
|
|
|
"success": bool,
|
|
|
"report_path": Path,
|
|
|
"error": str,
|
|
|
"stderr": str,
|
|
|
"stdout": str
|
|
|
}
|
|
|
"""
|
|
|
result = {
|
|
|
"success": False,
|
|
|
"report_path": output_xml,
|
|
|
"error": "",
|
|
|
"stderr": "",
|
|
|
"stdout": ""
|
|
|
}
|
|
|
|
|
|
# 查找 cppcheck 可执行文件
|
|
|
if cppcheck_path is None:
|
|
|
cppcheck_path = find_cppcheck_executable()
|
|
|
if cppcheck_path is None:
|
|
|
result["error"] = "未找到 cppcheck 可执行文件。请确保已安装 cppcheck 或在 PATH 中可用。"
|
|
|
return result
|
|
|
|
|
|
if not Path(cppcheck_path).exists():
|
|
|
result["error"] = f"cppcheck 可执行文件不存在: {cppcheck_path}"
|
|
|
return result
|
|
|
|
|
|
# 检查目标路径
|
|
|
if not target.exists():
|
|
|
result["error"] = f"目标路径不存在: {target}"
|
|
|
return result
|
|
|
|
|
|
# 确保输出目录存在
|
|
|
output_xml.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
# 构建 cppcheck 命令
|
|
|
cmd = [cppcheck_path]
|
|
|
|
|
|
if enable_all:
|
|
|
cmd.append("--enable=all")
|
|
|
|
|
|
if std:
|
|
|
cmd.append(f"--std={std}")
|
|
|
|
|
|
# 添加头文件搜索路径
|
|
|
if include_dirs:
|
|
|
for include_dir in include_dirs:
|
|
|
if Path(include_dir).exists():
|
|
|
cmd.extend(["-I", include_dir])
|
|
|
|
|
|
# 添加抑制规则
|
|
|
if suppress:
|
|
|
for rule_id in suppress:
|
|
|
cmd.extend(["--suppress", rule_id])
|
|
|
|
|
|
# XML 输出选项
|
|
|
cmd.extend(["--xml", "--xml-version=2"])
|
|
|
|
|
|
# 目标路径
|
|
|
cmd.append(str(target))
|
|
|
|
|
|
try:
|
|
|
print(f"正在运行 cppcheck: {' '.join(cmd)}")
|
|
|
print(f"目标: {target}")
|
|
|
print(f"输出: {output_xml}")
|
|
|
|
|
|
# 运行 cppcheck,将 stderr 重定向到 XML 文件(cppcheck 将错误输出到 stderr)
|
|
|
with open(output_xml, 'w', encoding='utf-8') as f:
|
|
|
process = subprocess.run(
|
|
|
cmd,
|
|
|
stdout=subprocess.PIPE,
|
|
|
stderr=f, # XML 输出到 stderr
|
|
|
text=True,
|
|
|
timeout=timeout,
|
|
|
cwd=target.parent if target.is_file() else target
|
|
|
)
|
|
|
|
|
|
result["stdout"] = process.stdout or ""
|
|
|
result["success"] = True
|
|
|
|
|
|
# 检查报告文件是否生成且非空
|
|
|
if output_xml.exists() and output_xml.stat().st_size > 0:
|
|
|
print(f"✓ cppcheck 报告已生成: {output_xml}")
|
|
|
print(f" 文件大小: {output_xml.stat().st_size} 字节")
|
|
|
else:
|
|
|
result["error"] = "cppcheck 报告文件为空或未生成"
|
|
|
result["success"] = False
|
|
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
result["error"] = f"cppcheck 执行超时({timeout}秒)"
|
|
|
except FileNotFoundError:
|
|
|
result["error"] = f"未找到 cppcheck 可执行文件: {cppcheck_path}"
|
|
|
except Exception as e:
|
|
|
result["error"] = f"运行 cppcheck 时发生错误: {str(e)}"
|
|
|
result["stderr"] = str(e)
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
def auto_detect_c_standard(target: Path) -> Optional[str]:
|
|
|
"""
|
|
|
根据文件扩展名自动检测 C/C++ 标准
|
|
|
|
|
|
Returns:
|
|
|
"c99" 或 "c++17" 或 None
|
|
|
"""
|
|
|
if target.is_file():
|
|
|
suffix = target.suffix.lower()
|
|
|
if suffix in ['.c', '.h']:
|
|
|
return "c99"
|
|
|
elif suffix in ['.cpp', '.cc', '.cxx', '.hpp', '.hxx']:
|
|
|
return "c++17"
|
|
|
elif target.is_dir():
|
|
|
# 检查目录中的文件
|
|
|
c_files = list(target.glob("*.c")) + list(target.glob("*.h"))
|
|
|
cpp_files = list(target.glob("*.cpp")) + list(target.glob("*.hpp"))
|
|
|
|
|
|
if c_files and not cpp_files:
|
|
|
return "c99"
|
|
|
elif cpp_files:
|
|
|
return "c++17"
|
|
|
|
|
|
return None
|
|
|
|