diff options
Diffstat (limited to 'ocamlbuild/rule.ml')
-rw-r--r-- | ocamlbuild/rule.ml | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/ocamlbuild/rule.ml b/ocamlbuild/rule.ml new file mode 100644 index 000000000..d496f8716 --- /dev/null +++ b/ocamlbuild/rule.ml @@ -0,0 +1,296 @@ +(***********************************************************************) +(* ocamlbuild *) +(* *) +(* Nicolas Pouillard, Berke Durak, projet Gallium, INRIA Rocquencourt *) +(* *) +(* Copyright 2007 Institut National de Recherche en Informatique et *) +(* en Automatique. All rights reserved. This file is distributed *) +(* under the terms of the Q Public License version 1.0. *) +(* *) +(***********************************************************************) + +(* $Id$ *) +(* Original author: Nicolas Pouillard *) +open My_std +open Format +open Log +open Outcome +module Resources = Resource.Resources + +exception Exit_rule_error of string + +type env = Pathname.t -> Pathname.t +type builder = Pathname.t list list -> (Pathname.t, exn) Outcome.t list +type action = env -> builder -> Command.t + +type t = + { name : string; + tags : Tags.t; + deps : Pathname.t list; + prods : Pathname.t list; + code : env -> builder -> Command.t } + +exception Code_digest of string * (bool -> unit) + +let compare _ _ = assert false + +let print_rule_name f r = pp_print_string f r.name + +let print_resource_list = List.print Resource.print + +let print_rule_contents f r = + fprintf f "@[<v2>{@ @[<2>name =@ %S@];@ @[<2>tags =@ %a@];@ @[<2>deps =@ %a@];@ @[<2>prods = %a@];@ @[<2>code = <fun>@]@]@ }" + r.name Tags.print r.tags print_resource_list r.deps print_resource_list r.prods + +let print = print_rule_name + +let subst env rule = + let subst_resources = List.map (Resource.subst env) in + let finder next_finder p = next_finder (Resource.subst env p) in + { (rule) with name = sbprintf "%s (%a)" rule.name Resource.print_env env; + prods = subst_resources rule.prods; + deps = subst_resources rule.deps; + code = (fun env -> rule.code (finder env)) } + +exception Can_produce of t + +let can_produce target rule = + try + List.iter begin fun resource -> + match Resource.matchit resource target with + | Some env -> raise (Can_produce (subst env rule)) + | None -> () + end rule.prods; None + with Can_produce r -> Some r + +let tags_matches tags r = if Tags.does_match tags r.tags then Some r else None + +let digest_prods r = + List.fold_right begin fun p acc -> + let f = Pathname.to_string (Pathname.in_build_dir p) in + if sys_file_exists f then (f, Digest.file f) :: acc else acc + end r.prods [] + +let digest_rule r dyndeps cmd_or_digest = + let buf = Buffer.create 1024 in + (match cmd_or_digest with + | Good cmd -> Buffer.add_string buf (Command.to_string_for_digest cmd) + | Bad(s, _) -> Buffer.add_string buf s); + let add_resource r = Buffer.add_string buf (Resource.Cache.digest_resource r) in + Buffer.add_string buf "prods:"; + List.iter add_resource r.prods; + Buffer.add_string buf "deps:"; + List.iter add_resource r.deps; + Buffer.add_string buf "dyndeps:"; + Resources.iter add_resource dyndeps; + Digest.string (Buffer.contents buf) + +let print_digest f x = pp_print_string f (Digest.to_hex x) + +let exists2 find p rs = + try Some (find p rs) with Not_found -> None + +let all_deps_of_tags = ref [] + +let cons deps acc = + List.fold_left begin fun acc dep -> + if List.mem dep acc then acc else dep :: acc + end acc deps + +let deps_of_tags tags = + List.fold_left begin fun acc (xtags, xdeps) -> + if Tags.does_match tags xtags then cons xdeps acc + else acc + end [] !all_deps_of_tags + +let set_deps_of_tags tags deps = + all_deps_of_tags := (tags, deps) :: !all_deps_of_tags + +let dep tags deps = set_deps_of_tags (Tags.of_list tags) deps + +let build_deps_of_tags builder tags = + match deps_of_tags tags with + | [] -> [] + | deps -> List.map Outcome.good (builder (List.map (fun x -> [x]) deps)) + +let build_deps_of_tags_on_cmd builder x = + let rec spec x = + match x with + | Command.N | Command.A _ | Command.Sh _ | Command.P _ | Command.Px _ | Command.V _ | Command.Quote _ -> () + | Command.S l -> List.iter spec l + | Command.T tags -> + begin match deps_of_tags tags with + | [] -> () + | deps -> List.iter ignore_good (builder (List.map (fun x -> [x]) deps)) + end in + let rec cmd x = + match x with + | Command.Nop -> () + | Command.Cmd(s) -> spec s + | Command.Seq(s) -> List.iter cmd s in + cmd x + +let call builder r = + let dyndeps = ref Resources.empty in + let builder rs = + let results = builder rs in + List.map begin fun res -> + match res with + | Good res' -> + let () = dprintf 10 "new dyndep for %S(%a): %S" r.name print_resource_list r.prods res' in + dyndeps := Resources.add res' !dyndeps; + List.iter (fun x -> Resource.Cache.add_dependency x res') r.prods; + res + | Bad _ -> res + end results in + let () = dprintf 5 "start rule %a" print r in + let cmd_or_digest = + try + let cmd = r.code (fun x -> x) builder in + build_deps_of_tags_on_cmd builder cmd; + Good cmd + with Code_digest(s, kont) -> Bad(s, kont) in + let dyndeps = !dyndeps in + let () = dprintf 10 "dyndeps: %a" Resources.print dyndeps in + let (reason, cached) = + match exists2 List.find (fun r -> not (Pathname.exists_in_build_dir r)) r.prods with + | Some r -> (`cache_miss_missing_prod r, false) + | _ -> + begin match exists2 List.find Resource.Cache.resource_has_changed r.deps with + | Some r -> (`cache_miss_changed_dep r, false) + | _ -> + begin match exists2 Resources.find Resource.Cache.resource_has_changed dyndeps with + | Some r -> (`cache_miss_changed_dyn_dep r, false) + | _ -> + begin match Resource.Cache.get_digest_for r.name with + | None -> (`cache_miss_no_digest, false) + | Some d -> + begin match cmd_or_digest with + | Bad("", _) -> + (`cache_miss_undigest, false) + | Bad(_, _) | Good(_) -> + let rule_digest = digest_rule r dyndeps cmd_or_digest in + if d = rule_digest then (`cache_hit, true) + else (`cache_miss_digest_changed(d, rule_digest), false) + end + end + end + end + in + let explain_reason l = + raw_dprintf (l+1) "mid rule %a: " print r; + match reason with + | `cache_miss_missing_prod r -> + dprintf l "cache miss: a product is not in build dir (%a)" Resource.print r + | `cache_miss_changed_dep r -> + dprintf l "cache miss: a dependency has changed (%a)" Resource.print r + | `cache_miss_changed_dyn_dep r -> + dprintf l "cache miss: a dynamic dependency has changed (%a)" Resource.print r + | `cache_miss_no_digest -> + dprintf l "cache miss: no digest found for %S (the command, a dependency, or a product)" + r.name + | `cache_hit -> dprintf (l+1) "cache hit" + | `cache_miss_digest_changed(old_d, new_d) -> + dprintf l "cache miss: the digest has changed for %S (the command, a dependency, or a product: %a <> %a)" + r.name print_digest old_d print_digest new_d + | `cache_miss_undigest -> + dprintf l "cache miss: cache not supported for the rule %S" r.name in + let prod_digests = digest_prods r in + (if not cached then List.iter Resource.clean r.prods); + (if !Options.nothing_should_be_rebuilt && not cached then + (explain_reason (-1); + let msg = sbprintf "Need to rebuild %a through the rule `%a'" print_resource_list r.prods print r in + raise (Exit_rule_error msg))); + explain_reason 3; + let kont = begin fun () -> + try + (match cmd_or_digest with + | Good cmd -> if cached then Command.execute ~pretend:true cmd + | Bad (_, kont) -> kont cached); + List.iter Resource.Cache.resource_built r.prods; + (if not cached then + let new_rule_digest = digest_rule r dyndeps cmd_or_digest in + let new_prod_digests = digest_prods r in + let () = Resource.Cache.store_digest r.name new_rule_digest in + List.iter begin fun p -> + let f = Pathname.to_string (Pathname.in_build_dir p) in + (try let digest = List.assoc f prod_digests in + let new_digest = List.assoc f new_prod_digests in + if digest <> new_digest then raise Not_found + with Not_found -> Resource.Cache.resource_changed p) + end r.prods); + dprintf 5 "end rule %a" print r + with exn -> (List.iter Resource.clean r.prods; raise exn) + end in + match cmd_or_digest with + | Good cmd when not cached -> + List.iter (fun x -> Resource.Cache.suspend_resource x cmd kont r.prods) r.prods + | Bad _ | Good _ -> kont () + +let (get_rules, add_rule) = + let rules = ref [] in + (fun () -> !rules), + begin fun pos r -> + try + let _ = List.find (fun x -> x.name = r.name) !rules in + raise (Exit_rule_error (sbprintf "Rule.add_rule: already exists: (%a)" print r)) + with Not_found -> + match pos with + | `bottom -> rules := !rules @ [r] + | `top -> rules := r :: !rules + | `after s -> + rules := + List.fold_right begin fun x acc -> + if x.name = s then x :: r :: acc else x :: acc + end !rules [] + | `before s -> + rules := + List.fold_right begin fun x acc -> + if x.name = s then r :: x :: acc else x :: acc + end !rules [] + end + +let rule name ?(tags=[]) ?(prods=[]) ?(deps=[]) ?prod ?dep ?(insert = `bottom) code = + let res_add x acc = + let x = Resource.import x in + if List.mem x acc then + failwith (sprintf "in rule %s, multiple occurences of the resource %s" name x) + else x :: acc in + let res_of_opt = function None -> [] | Some r -> [Resource.import r] in + if prods = [] && prod = None then raise (Exit_rule_error "Can't make a rule that produce nothing"); + add_rule insert + { name = name; + tags = List.fold_right Tags.add tags Tags.empty; + deps = List.fold_right res_add deps (res_of_opt dep); + prods = List.fold_right res_add prods (res_of_opt prod); + code = code } + +let file_rule name ?tags ~prod ?deps ?dep ?insert ~cache action = + rule name ?tags ~prod ?dep ?deps ?insert begin fun env _ -> + raise (Code_digest (cache env, (fun cached -> + if not cached then + with_output_file (env prod) (action env)))) + end + +let custom_rule name ?tags ?prods ?prod ?deps ?dep ?insert ~cache action = + rule name ?tags ?prods ?prod ?dep ?deps ?insert begin fun env _ -> + raise (Code_digest (cache env, fun cached -> action env ~cached)) + end + +module Common_commands = struct + open Command + let mv src dest = Cmd (S [A"mv"; P src; Px dest]) + let cp src dest = Cmd (S [A"cp"; P src; Px dest]) + let ln_f pointed pointer = Cmd (S [A"ln"; A"-f"; P pointed; Px pointer]) + let ln_s pointed pointer = Cmd (S[A"ln"; A"-s"; P pointed; Px pointer]) + let rm_f x = Cmd (S [A"rm"; A"-f"; Px x]) + let touch file = Cmd (S[A"touch"; Px file]) + let chmod opts file = Cmd (S[A"chmod"; opts; Px file]) + let cmp a b = Cmd (S[A"cmp"; P a; Px b]) +end +open Common_commands + +let copy_rule name ?insert src dest = + rule name ?insert ~prod:dest ~dep:src + (fun env _ -> cp (env src) (env dest)) + |