fester/backend/api/build.py

86 lines
2.8 KiB
Python

"""
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)