188 lines
5.6 KiB
Python
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()) |
