Indexes producers by output path, so a generator and the compile consuming its output are connected without being declared. Detects duplicate producers and reports dependency cycles by path.
73 lines
2.0 KiB
OCaml
73 lines
2.0 KiB
OCaml
type node = {
|
|
id : int;
|
|
tag : string;
|
|
label : string;
|
|
cmd : string array;
|
|
outs : string list;
|
|
ins : string list;
|
|
depfile : string option;
|
|
ords : string list;
|
|
pool : string;
|
|
mutable deps : int list;
|
|
}
|
|
|
|
type t = { nodes : node array; by_output : (string, int) Hashtbl.t }
|
|
|
|
let make ~id ~tag ~label ~cmd ~outs ~ins ?depfile ?(ords = []) ?(pool = "default") () =
|
|
{ id; tag; label; cmd; outs; ins; depfile; ords; pool; deps = [] }
|
|
|
|
let build specs =
|
|
let nodes = Array.of_list specs in
|
|
Array.iteri (fun i n -> if n.id <> i then invalid_arg "graph: node ids must be dense") nodes;
|
|
let by_output = Hashtbl.create (Array.length nodes * 2) in
|
|
Array.iter
|
|
(fun n ->
|
|
List.iter
|
|
(fun o ->
|
|
match Hashtbl.find_opt by_output o with
|
|
| Some prev when prev <> n.id ->
|
|
Diag.error "two rules both produce %s (%s and %s)" o nodes.(prev).label n.label
|
|
| _ -> Hashtbl.replace by_output o n.id)
|
|
n.outs)
|
|
nodes;
|
|
Array.iter
|
|
(fun n ->
|
|
n.deps <-
|
|
List.filter_map (fun i -> Hashtbl.find_opt by_output i) (n.ins @ n.ords)
|
|
|> List.sort_uniq compare
|
|
|> List.filter (fun d -> d <> n.id))
|
|
nodes;
|
|
{ nodes; by_output }
|
|
|
|
let topo g =
|
|
let n = Array.length g.nodes in
|
|
let state = Array.make n 0 in
|
|
let order = ref [] in
|
|
let rec visit path i =
|
|
match state.(i) with
|
|
| 2 -> ()
|
|
| 1 ->
|
|
let cycle = List.rev_map (fun j -> g.nodes.(j).label) (i :: path) in
|
|
Diag.error "dependency cycle: %s" (String.concat " -> " cycle)
|
|
| _ ->
|
|
state.(i) <- 1;
|
|
List.iter (visit (i :: path)) g.nodes.(i).deps;
|
|
state.(i) <- 2;
|
|
order := i :: !order
|
|
in
|
|
for i = 0 to n - 1 do visit [] i done;
|
|
List.rev !order
|
|
|
|
let reachable g roots =
|
|
let seen = Hashtbl.create 64 in
|
|
let rec go i =
|
|
if not (Hashtbl.mem seen i) then begin
|
|
Hashtbl.add seen i ();
|
|
List.iter go g.nodes.(i).deps
|
|
end
|
|
in
|
|
List.iter go roots;
|
|
seen
|
|
|
|
let producing g path = Hashtbl.find_opt g.by_output path
|