///|
/// A fully materialized, deterministic plan for one requested target.
/// Unlike a scheduler, this value is safe to inspect, serialize, benchmark,
/// or hand to a native/WASM host before any command is executed.
pub(all) struct MaterializedPlan {
target : String
edges : Array[BuildEdge]
waves : Array[Array[BuildEdge]]
commands : Array[String]
inputs : Array[String]
outputs : Array[String]
critical_path : Int
} derive(Debug, Eq)
///|
pub fn MaterializedPlan::to_text(self : MaterializedPlan) -> String {
"target=" +
self.target +
" edges=" +
self.edges.length().to_string() +
" waves=" +
self.waves.length().to_string() +
" critical_path=" +
self.critical_path.to_string() +
" inputs=" +
self.inputs.length().to_string() +
" outputs=" +
self.outputs.length().to_string() +
" commands=" +
self.commands.length().to_string()
}
///|
fn plan_has_duplicate_output(edges : Array[BuildEdge]) -> String? {
let producers : Map[String, String] = Map([])
for edge in edges {
for output in edge.outputs {
match producers.get(output) {
Some(previous) =>
return Some(
"duplicate output producer: " +
output +
" from " +
previous +
" and " +
edge.key(),
)
None => producers[output] = edge.key()
}
}
}
None
}
///|
fn plan_unique_values(
edges : Array[BuildEdge],
output_mode : Bool,
) -> Array[String] {
let values : Array[String] = []
for edge in edges {
let candidates = if output_mode { edge.outputs } else { edge.inputs }
for item in candidates {
if !values.contains(item) {
values.push(item)
}
}
}
values
}
///|
fn plan_leaf_inputs(edges : Array[BuildEdge]) -> Array[String] {
let produced : Map[String, Bool] = Map([])
for edge in edges {
for output in edge.outputs {
produced[output] = true
}
}
let leaves : Array[String] = []
for edge in edges {
for input in edge.inputs {
if !produced.contains(input) && !leaves.contains(input) {
leaves.push(input)
}
}
}
leaves
}
///|
/// Materialize one target with stable traversal order and explicit variables.
pub fn Manifest::materialize_plan(
self : Manifest,
target : String,
variables : Map[String, String],
) -> Result[MaterializedPlan, String] {
let produced : Map[String, Bool] = Map([])
for edge in self.builds {
for output in edge.outputs {
produced[output] = true
}
}
if !produced.contains(target) {
return Err("target is not produced: " + target)
}
match plan_has_duplicate_output(self.builds) {
Some(error) => return Err(error)
None => ()
}
match self.validate() {
Err(error) => Err(error)
Ok(_) => {
let graph = DepGraph::build(self)
let edges = match graph.traverse(target) {
Err(error) => return Err(error)
Ok(value) => value
}
let waves = match graph.parallel_waves(target) {
Err(error) => return Err(error)
Ok(value) => value
}
let commands : Array[String] = []
for edge in edges {
match edge.render_command_with_variables(self.rules, variables) {
Err(error) => return Err(error)
Ok(command) => commands.push(command)
}
}
Ok({
target,
edges,
waves,
commands,
inputs: plan_leaf_inputs(edges),
outputs: plan_unique_values(edges, true),
critical_path: waves.length(),
})
}
}
}