""" Build endpoint — kicks off a pipeline run for a BuildSpec. NOTE: This is the thin API wrapper. The real scheduling + execution lives in backend/pipeline/engine.py and backend/scheduler/. """ from backend.api.build_spec import BuildSpec from backend.pipeline.engine import PipelineEngine from backend.graph.build_graph import BuildGraph from backend.api.api import bus, registry # singletons async def build_endpoint(spec, nodes, cluster=None, intelligence=None): """ Build a project. `spec` may be: - a BuildSpec instance - a dict (will be coerced via BuildSpec.from_request) - a raw dict matching the legacy BuildGraph constructor (with `target`/`output_mode`) `nodes`, `cluster`, `intelligence` are accepted for backward-compat but only `nodes` is actually used (the engine has its own node registry). """ # Coerce spec into something BuildGraph can consume. # BuildGraph reads spec.arch, spec.target, spec.toolchain, spec.output_mode. # BuildSpec (the dataclass) doesn't have target/output_mode, so we adapt. if isinstance(spec, BuildSpec): bs = spec elif isinstance(spec, dict): # If caller passed a raw dict with `target`/`output_mode`, wrap it # in a tiny shim object so BuildGraph's attribute access works. if "target" in spec or "output_mode" in spec: bs = _ShimSpec(spec) else: try: bs = BuildSpec.from_request(spec) except KeyError: bs = _ShimSpec(spec) else: # Already a shim or has the attributes BuildGraph expects bs = spec # Generate the DAG try: graph = BuildGraph(bs).generate() except Exception as e: # In production we'd log this; for now bubble up a structured error return {"status": "error", "error": f"build_graph_failed: {e}"} # Run the pipeline engine = PipelineEngine(nodes, registry, bus) try: results = await engine.run(bs) except Exception as e: return {"status": "error", "error": f"pipeline_failed: {e}"} return { "status": "complete", "actions": [{"name": n, "state": s} for n, s in results], "graph_size": len(graph), } class _ShimSpec: """Adapter that exposes attribute access over a dict, so BuildGraph can read spec.target / spec.output_mode / spec.arch / spec.toolchain regardless of which shape the caller supplied.""" def __init__(self, d: dict): self._d = d def __getattr__(self, name): if name in self._d: return self._d[name] # Sensible defaults for fields BuildGraph reads defaults = { "arch": "x86_64", "target": "linux-gnu", "toolchain": "gcc", "output_mode": "tar", "project": "unknown", } return defaults.get(name)