ocaml/ocamlbuild/solver.ml

136 lines
5.6 KiB
OCaml
Raw Normal View History

(***********************************************************************)
(* *)
(* 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 GNU Library General Public License, with *)
(* the special exception on linking described in file ../LICENSE. *)
(* *)
(***********************************************************************)
(* Original author: Nicolas Pouillard *)
open My_std
open Log
open Format
open Outcome
type backtrace =
| Leaf of Pathname.t
| Choice of backtrace list
| Depth of Pathname.t * backtrace
| Target of string * backtrace
exception Failed of backtrace
exception Circular of Pathname.t * Pathname.t list
let failed target backtrace =
Resource.Cache.resource_failed target;
raise (Failed backtrace)
let rec pp_repeat f (n, s) =
if n > 0 then (pp_print_string f s; pp_repeat f (n - 1, s))
(* Targets must be normalized pathnames.
* Recursive calls are either on input targets
* or dependencies of these targets (returned by Rule.deps_of_rule).
*)
let rec self depth on_the_go_orig target =
let rules = Rule.get_rules () in
let on_the_go = target :: on_the_go_orig in
(* skip allocating fmt on a hot path *)
if is_logging 4 then dprintf 4 "==%a> %a" pp_repeat (depth, "==") Resource.print target;
if List.mem target on_the_go_orig then raise (Circular(target, on_the_go_orig));
match Resource.Cache.resource_state target with
| Resource.Cache.Bbuilt ->
(if is_logging 5 then dprintf 5 "%a already built" Resource.print target)
| Resource.Cache.Bcannot_be_built ->
(if is_logging 5 then dprintf 5 "%a already failed" Resource.print target; failed target (Leaf target))
| Resource.Cache.Bsuspension(s) ->
(if is_logging 5 then dprintf 5 "%a was suspended -> resuming" Resource.print target;
Resource.Cache.resume_suspension s)
| Resource.Cache.Bnot_built_yet ->
if not (Pathname.is_relative target) && Pathname.exists target then
if Resource.Cache.external_is_up_to_date target then ()
else (* perhaps the error can be refined *) failed target (Leaf target)
else
if Resource.exists_in_source_dir target then
Resource.Cache.import_in_build_dir target
else
match List.filter_opt (Rule.can_produce target) rules with
| [] -> failed target (Leaf target)
| matching_rules ->
let rec until_works rs backtraces =
match rs with
| [] -> assert false
| r :: rs ->
try
List.iter (force_self (depth + 1) on_the_go) (Rule.deps_of_rule r);
try
Rule.call (self_firsts (depth + 1) on_the_go) r
with Rule.Failed -> raise (Failed (Leaf target))
with Failed backtrace ->
if rs = [] then failed target (Depth (target, Choice (backtrace :: backtraces)))
else
let () =
match backtrace with
| Depth (top_prod, _) -> Resource.Cache.clear_resource_failed top_prod
| Target _ | Choice _ | Leaf _ -> ()
in until_works rs (backtrace :: backtraces)
in until_works matching_rules []
(* Build the first target that is buildable *)
and self_first depth on_the_go already_failed rs =
match rs with
| [] -> Bad (Failed (Choice already_failed))
| r :: rs ->
try self depth on_the_go r; Good r
with Failed backtrace -> self_first depth on_the_go (backtrace :: already_failed) rs
(* This variant is the one (once partially applied) called the 'build'
* function in the rule actions.
*
* This one takes a list of list of pathnames to build.
* This is a parallel conjonction of sequential alternatives.
* This means that in each sublist of pathnames, the first
* target that is buildable will be picked. The outer list
* denotes that one can build each target in parallel.
*)
and self_firsts depth on_the_go rss =
let results = List.map (self_first depth on_the_go []) rss in
let cmds, thunks =
List.fold_right begin fun res ((acc1, acc2) as acc) ->
match res with
| Bad _ -> acc
| Good res ->
match Resource.Cache.get_optional_resource_suspension res with
| None -> acc
| Some (cmd, thunk) -> (cmd :: acc1, thunk :: acc2)
end results ([], []) in
let count = List.length cmds in
let job_debug = if !Command.jobs = 1 then 10 else 5 in
(* skip allocating fmt on a hot path *)
if is_logging job_debug && count > 1 then dprintf job_debug ">>> PARALLEL: %d" count;
let opt_exn = Command.execute_many cmds in
if is_logging job_debug && count > 1 then dprintf job_debug "<<< PARALLEL";
begin match opt_exn with
| Some(res, exn) ->
List.iter2 (fun res thunk -> if res then thunk ()) res thunks;
Log.finish ~how:`Error ();
raise exn
| None ->
List.iter (fun thunk -> thunk ()) thunks
end;
results
and force_self depth on_the_go x = self depth on_the_go x; Resource.Cache.resume_resource x
let solve = force_self 0 []
let solve_target name rs =
match self_first 0 [] [] rs with
| Good res -> Resource.Cache.resume_resource res; res
| Bad (Failed backtrace) -> raise (Failed (Target (name, backtrace)))
| Bad exn -> raise exn