""" Build action graph — convert a project spec into a DAG of executable actions. Supports two input shapes: 1. Legacy: project = {"targets": {"name": "cmd", ...}} → flat list of independent actions 2. Rich: project = {"stages": [{"name": "...", "command": "...", "deps": [...], "target": "..."}, ...]} → real DAG with dependency edges """ from backend.graph.actions import hash_action def build_action_graph(project): """ Convert a project spec into a list of action dicts. Each action has: - name: unique action name - command: shell command to run - inputs: list of input globs - outputs: list of output paths - deps: list of action names this depends on - env: environment dict - runtime: "host" | "lxc" | "libvirt" - target: build target name (e.g. "x86_64-linux-gnu") - hash: sha256 of the action dict (for cache keys) """ graph = [] if "stages" in project: # Rich DAG input build_dir = project.get("build_dir") for stage in project["stages"]: action = { "name": stage["name"], "command": stage.get("command", "true"), "inputs": stage.get("inputs", ["src/**"]), "outputs": stage.get("outputs", [f"build/{stage['name']}.out"]), "deps": stage.get("deps", []), "env": stage.get("env", project.get("build_env", {})), "runtime": stage.get("runtime", "host"), "target": stage.get("target", project.get("default_target", "native")), "cost": stage.get("cost", 1), "dir": stage.get("dir", build_dir), } action["hash"] = hash_action(action) graph.append(action) return graph if "targets" in project: # Legacy flat input — synthesize a minimal pipeline: # fetch_source → configure → per-target build → package default_target = project.get("default_target", "native") build_env = project.get("build_env", {}) build_dir = project.get("build_dir") # Always include a fetch + configure stage graph.append(_make_action( name="fetch_source", command=f"git clone {project.get('source', project.get('repo', '.'))} .", deps=[], target=default_target, env=build_env, cost=2, dir=build_dir, )) graph.append(_make_action( name="configure", command="./configure", deps=["fetch_source"], target=default_target, env=build_env, cost=1, dir=build_dir, )) # Per-target build actions (all depend on configure) for target_name, cmd in project["targets"].items(): graph.append(_make_action( name=f"build_{target_name}", command=cmd, deps=["configure"], target=target_name, env=build_env, cost=3, dir=build_dir, )) # Package stage depends on all builds build_names = [f"build_{t}" for t in project["targets"].keys()] graph.append(_make_action( name="package", command="tar czf build.tar.gz build/", deps=build_names, target=default_target, env=build_env, cost=2, dir=build_dir, )) return graph # Fallback: empty graph return graph def _make_action(name, command, deps, target, env, cost=1, dir=None): action = { "name": name, "command": command, "inputs": ["src/**"], "outputs": [f"build/{name}.out"], "deps": deps, "env": env, "runtime": "host", "target": target, "cost": cost, "dir": dir, # working directory for the command } action["hash"] = hash_action(action) return action