Files
python-mcp/tools/cmake.py
AuroraCrimsonRose e471f9bc54 Added many tools
2026-06-03 06:01:06 -05:00

188 lines
5.6 KiB
Python

from __future__ import annotations
from typing import Any
from pathlib import Path
from core.tools.base import BaseTool, ToolContext
from core.tools.registry import registry
from core.safety import safety
from core.events import bus
from core.subprocess import run_command
class CMakeTool(BaseTool):
name = "cmake"
description = "CMake build system operations (configure, build, generate, clean)"
# =========================================================
# EXECUTE ROUTER
# =========================================================
def execute(self, payload: dict[str, Any], ctx: ToolContext):
action = str(payload.get("action", "")).strip()
bus.log(
"CMAKE",
"cmake_execute",
"INFO",
{"action": action}
)
match action:
case "configure":
return self.configure(payload)
case "build":
return self.build(payload)
case "clean":
return self.clean(payload)
case "generate":
return self.generate(payload)
case _:
raise ValueError(f"Unknown cmake action: {action}")
# =========================================================
# HELPERS
# =========================================================
def _path(self, value: str) -> Path:
return safety.validate_path(value)
# =========================================================
# CONFIGURE
# =========================================================
def configure(self, payload: dict[str, Any]):
source_dir = payload.get("source_dir", ".")
build_dir = payload.get("build_dir", "build")
generator = payload.get("generator") # optional
build_type = payload.get("build_type", "Release")
if not isinstance(source_dir, str):
raise ValueError("source_dir must be string")
if not isinstance(build_dir, str):
raise ValueError("build_dir must be string")
src = self._path(source_dir)
bld = self._path(build_dir)
bld.mkdir(parents=True, exist_ok=True)
cmd = [
"cmake",
"-S", str(src),
"-B", str(bld),
f"-DCMAKE_BUILD_TYPE={build_type}"
]
if generator:
if not isinstance(generator, str):
raise ValueError("generator must be string")
cmd.extend(["-G", generator])
result = run_command(cmd=cmd)
return {
"action": "configure",
"source_dir": str(src),
"build_dir": str(bld),
"status": "success" if result.get("return_code") == 0 else "error",
"stdout": result.get("stdout", ""),
"stderr": result.get("stderr", "")
}
# =========================================================
# BUILD
# =========================================================
def build(self, payload: dict[str, Any]):
build_dir = payload.get("build_dir", "build")
target = payload.get("target") # optional
jobs = payload.get("jobs", 0)
if not isinstance(build_dir, str):
raise ValueError("build_dir must be string")
bld = self._path(build_dir)
cmd = ["cmake", "--build", str(bld)]
if target:
if not isinstance(target, str):
raise ValueError("target must be string")
cmd.extend(["--target", target])
if isinstance(jobs, int) and jobs > 0:
cmd.extend(["--parallel", str(jobs)])
result = run_command(cmd=cmd)
return {
"action": "build",
"build_dir": str(bld),
"target": target,
"status": "success" if result.get("return_code") == 0 else "error",
"stdout": result.get("stdout", ""),
"stderr": result.get("stderr", "")
}
# =========================================================
# GENERATE (alias convenience)
# =========================================================
def generate(self, payload: dict[str, Any]):
# CMake modern workflow usually doesn't need this separately,
# but kept for explicit "generate-only" workflows.
return self.configure(payload)
# =========================================================
# CLEAN
# =========================================================
def clean(self, payload: dict[str, Any]):
build_dir = payload.get("build_dir", "build")
if not isinstance(build_dir, str):
raise ValueError("build_dir must be string")
bld = self._path(build_dir)
if not bld.exists():
return {
"action": "clean",
"status": "skipped",
"message": "build directory does not exist"
}
# safe clean: only remove cache artifacts, not full directory unless requested
cache_file = bld / "CMakeCache.txt"
removed = []
if cache_file.exists():
cache_file.unlink()
removed.append("CMakeCache.txt")
# optional: remove CMakeFiles
cmake_files = bld / "CMakeFiles"
if cmake_files.exists():
import shutil
shutil.rmtree(cmake_files)
removed.append("CMakeFiles/")
return {
"action": "clean",
"build_dir": str(bld),
"removed": removed,
"status": "ok"
}
# =========================================================
# REGISTER
# =========================================================
registry.register(CMakeTool())