124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
"""
|
|
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
|