423 lines
12 KiB
OCaml
423 lines
12 KiB
OCaml
(***********************************************************************)
|
|
(* *)
|
|
(* 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. *)
|
|
(* *)
|
|
(***********************************************************************)
|
|
|
|
|
|
(* Original author: Nicolas Pouillard *)
|
|
(* Command *)
|
|
|
|
open My_std
|
|
open Log
|
|
|
|
type tags = Tags.t
|
|
type pathname = string
|
|
|
|
let jobs = ref 1
|
|
|
|
type t =
|
|
| Seq of t list
|
|
| Cmd of spec
|
|
| Echo of string list * pathname
|
|
| Nop
|
|
and spec =
|
|
| N (* nop or nil *)
|
|
| S of spec list
|
|
| A of string
|
|
| P of pathname
|
|
| Px of pathname
|
|
| Sh of string
|
|
| T of Tags.t
|
|
| V of string
|
|
| Quote of spec
|
|
|
|
(*type v = [ `Seq of v list | `Cmd of vspec | `Nop ]
|
|
and vspec =
|
|
[ `N
|
|
| `S of vspec list
|
|
| `A of string
|
|
| `P of pathname
|
|
| `Px of pathname
|
|
| `Sh of string
|
|
| `Quote of vspec ]
|
|
|
|
let rec spec_of_vspec =
|
|
function
|
|
| `N -> N
|
|
| `S vspecs -> S (List.map spec_of_vspec vspecs)
|
|
| `A s -> A s
|
|
| `P s -> P s
|
|
| `Px s -> Px s
|
|
| `Sh s -> Sh s
|
|
| `Quote vspec -> Quote (spec_of_vspec vspec)
|
|
|
|
let rec vspec_of_spec =
|
|
function
|
|
| N -> `N
|
|
| S specs -> `S (List.map vspec_of_spec specs)
|
|
| A s -> `A s
|
|
| P s -> `P s
|
|
| Px s -> `Px s
|
|
| Sh s -> `Sh s
|
|
| T _ -> invalid_arg "vspec_of_spec: T not supported"
|
|
| Quote spec -> `Quote (vspec_of_spec spec)
|
|
|
|
let rec t_of_v =
|
|
function
|
|
| `Nop -> Nop
|
|
| `Cmd vspec -> Cmd (spec_of_vspec vspec)
|
|
| `Seq cmds -> Seq (List.map t_of_v cmds)
|
|
|
|
let rec v_of_t =
|
|
function
|
|
| Nop -> `Nop
|
|
| Cmd spec -> `Cmd (vspec_of_spec spec)
|
|
| Seq cmds -> `Seq (List.map v_of_t cmds)*)
|
|
|
|
let no_tag_handler _ = failwith "no_tag_handler"
|
|
|
|
let tag_handler = ref no_tag_handler
|
|
|
|
(*** atomize *)
|
|
let atomize l = S(List.map (fun x -> A x) l)
|
|
let atomize_paths l = S(List.map (fun x -> P x) l)
|
|
(* ***)
|
|
|
|
let env_path = lazy begin
|
|
let path_var = Sys.getenv "PATH" in
|
|
let parse_path =
|
|
if Sys.os_type = "Win32" then
|
|
Lexers.parse_environment_path_w
|
|
else
|
|
Lexers.parse_environment_path
|
|
in
|
|
let paths =
|
|
try
|
|
parse_path (Lexing.from_string path_var)
|
|
with Lexers.Error msg -> raise (Lexers.Error ("$PATH: " ^ msg))
|
|
in
|
|
let norm_current_dir_name path =
|
|
if path = "" then Filename.current_dir_name else path
|
|
in
|
|
List.map norm_current_dir_name paths
|
|
end
|
|
|
|
let virtual_solvers = Hashtbl.create 32
|
|
let setup_virtual_command_solver virtual_command solver =
|
|
Hashtbl.replace virtual_solvers virtual_command solver
|
|
let virtual_solver virtual_command =
|
|
let solver =
|
|
try
|
|
Hashtbl.find virtual_solvers virtual_command
|
|
with Not_found ->
|
|
failwith (sbprintf "no solver for the virtual command %S \
|
|
(setup one with Command.setup_virtual_command_solver)"
|
|
virtual_command)
|
|
in
|
|
try solver ()
|
|
with Not_found ->
|
|
failwith (Printf.sprintf "the solver for the virtual command %S \
|
|
has failed finding a valid command" virtual_command)
|
|
|
|
(* On Windows, we need to also check for the ".exe" version of the file. *)
|
|
let file_or_exe_exists file =
|
|
sys_file_exists file || Sys.os_type = "Win32" && sys_file_exists (file ^ ".exe")
|
|
|
|
let search_in_path cmd =
|
|
(* Try to find [cmd] in path [path]. *)
|
|
let try_path path =
|
|
(* Don't know why we're trying to be subtle here... *)
|
|
if path = Filename.current_dir_name then file_or_exe_exists cmd
|
|
else file_or_exe_exists (filename_concat path cmd)
|
|
in
|
|
if Filename.is_implicit cmd then
|
|
let path = List.find try_path !*env_path in
|
|
(* We're not trying to append ".exe" here because all windows shells are
|
|
* capable of understanding the command without the ".exe" suffix. *)
|
|
filename_concat path cmd
|
|
else
|
|
cmd
|
|
|
|
(*** string_of_command_spec{,_with_calls *)
|
|
let rec string_of_command_spec_with_calls call_with_tags call_with_target resolve_virtuals spec =
|
|
let self = string_of_command_spec_with_calls call_with_tags call_with_target resolve_virtuals in
|
|
let b = Buffer.create 256 in
|
|
(* The best way to prevent bash from switching to its windows-style
|
|
* quote-handling is to prepend an empty string before the command name. *)
|
|
if Sys.os_type = "Win32" then
|
|
Buffer.add_string b "''";
|
|
let first = ref true in
|
|
let put_space () =
|
|
if !first then
|
|
first := false
|
|
else
|
|
Buffer.add_char b ' '
|
|
in
|
|
let put_filename p =
|
|
Buffer.add_string b (Shell.quote_filename_if_needed p)
|
|
in
|
|
let rec do_spec = function
|
|
| N -> ()
|
|
| A u -> put_space (); put_filename u
|
|
| Sh u -> put_space (); Buffer.add_string b u
|
|
| P p -> put_space (); put_filename p
|
|
| Px u -> put_space (); put_filename u; call_with_target u
|
|
| V v -> if resolve_virtuals then do_spec (virtual_solver v)
|
|
else (put_space (); Printf.bprintf b "<virtual %s>" (Shell.quote_filename_if_needed v))
|
|
| S l -> List.iter do_spec l
|
|
| T tags -> call_with_tags tags; do_spec (!tag_handler tags)
|
|
| Quote s -> put_space (); put_filename (self s)
|
|
in
|
|
do_spec spec;
|
|
Buffer.contents b
|
|
|
|
let string_of_command_spec x = string_of_command_spec_with_calls ignore ignore false x
|
|
|
|
let string_target_and_tags_of_command_spec spec =
|
|
let rtags = ref Tags.empty in
|
|
let rtarget = ref "" in
|
|
let union_rtags tags = rtags := Tags.union !rtags tags in
|
|
let s = string_of_command_spec_with_calls union_rtags ((:=) rtarget) true spec in
|
|
let target = if !rtarget = "" then s else !rtarget in
|
|
s, target, !rtags
|
|
|
|
let string_print_of_command_spec spec quiet pretend =
|
|
let s, target, tags = string_target_and_tags_of_command_spec spec in
|
|
fun () -> if not quiet then Log.event ~pretend s target tags; s
|
|
(* ***)
|
|
|
|
let print_escaped_string f = Format.fprintf f "%S"
|
|
|
|
let rec print f =
|
|
function
|
|
| Cmd spec -> Format.pp_print_string f (string_of_command_spec spec)
|
|
| Seq seq -> List.print print f seq
|
|
| Nop -> Format.pp_print_string f "nop"
|
|
| Echo(texts, dest_path) ->
|
|
Format.fprintf f "@[<2>Echo(%a,@ %a)@]"
|
|
(List.print print_escaped_string) texts print_escaped_string dest_path
|
|
|
|
let to_string x = sbprintf "%a" print x
|
|
|
|
let add_parallel_stat, dump_parallel_stats =
|
|
let xmin = ref max_int in
|
|
let xmax = ref 0 in
|
|
let xsum = ref 0 in
|
|
let xsumall = ref 0 in
|
|
let xcount = ref 0 in
|
|
let xcountall = ref 0 in
|
|
let add_parallel_stat x =
|
|
if x > 0 then begin
|
|
incr xcountall;
|
|
xsumall := x + !xsumall;
|
|
end;
|
|
if x > 1 then begin
|
|
incr xcount;
|
|
xsum := x + !xsum;
|
|
xmax := max !xmax x;
|
|
xmin := min !xmin x;
|
|
end
|
|
in
|
|
let dump_parallel_stats () =
|
|
if !jobs <> 1 then
|
|
if !xcount = 0 then
|
|
dprintf 1 "# No parallelism done"
|
|
else
|
|
let xaverage = float_of_int !xsumall /. float_of_int !xcountall in
|
|
let xaveragepara = float_of_int !xsum /. float_of_int !xcount in
|
|
dprintf 1 "# Parallel statistics: { count(total): %d(%d), max: %d, min: %d, average(total): %.3f(%.3f) }"
|
|
!xcount !xcountall !xmax !xmin xaveragepara xaverage
|
|
in
|
|
add_parallel_stat, dump_parallel_stats
|
|
|
|
module Primitives = struct
|
|
let do_echo texts dest_path =
|
|
with_output_file dest_path begin fun oc ->
|
|
List.iter (output_string oc) texts
|
|
end
|
|
let echo x y () = (* no print here yet *) do_echo x y; ""
|
|
end
|
|
|
|
let rec list_rev_iter f =
|
|
function
|
|
| [] -> ()
|
|
| x :: xs -> list_rev_iter f xs; f x
|
|
|
|
let flatten_commands quiet pretend cmd =
|
|
let rec loop acc =
|
|
function
|
|
| [] -> acc
|
|
| Nop :: xs -> loop acc xs
|
|
| Cmd spec :: xs -> loop (string_print_of_command_spec spec quiet pretend :: acc) xs
|
|
| Echo(texts, dest_path) :: xs -> loop (Primitives.echo texts dest_path :: acc) xs
|
|
| Seq l :: xs -> loop (loop acc l) xs
|
|
in List.rev (loop [] [cmd])
|
|
|
|
let execute_many ?(quiet=false) ?(pretend=false) cmds =
|
|
add_parallel_stat (List.length cmds);
|
|
let degraded = !*My_unix.is_degraded || Sys.os_type = "Win32" in
|
|
let jobs = !jobs in
|
|
if jobs < 0 then invalid_arg "jobs < 0";
|
|
let max_jobs = if jobs = 0 then None else Some jobs in
|
|
|
|
let ticker = Log.update in
|
|
let display = Log.display in
|
|
|
|
if cmds = [] then
|
|
None
|
|
else
|
|
begin
|
|
let konts = List.map (flatten_commands quiet pretend) cmds in
|
|
if pretend then
|
|
begin
|
|
List.iter (List.iter (fun f -> ignore (f ()))) konts;
|
|
None
|
|
end
|
|
else
|
|
begin
|
|
reset_filesys_cache ();
|
|
if degraded then
|
|
let res, opt_exn =
|
|
List.fold_left begin fun (acc_res, acc_exn) cmds ->
|
|
match acc_exn with
|
|
| None ->
|
|
begin try
|
|
List.iter begin fun action ->
|
|
let cmd = action () in
|
|
let rc = sys_command cmd in
|
|
if rc <> 0 then begin
|
|
if not quiet then
|
|
eprintf "Exit code %d while executing this \
|
|
command:@\n%s" rc cmd;
|
|
raise (Exit_with_code rc)
|
|
end
|
|
end cmds;
|
|
true :: acc_res, None
|
|
with e -> false :: acc_res, Some e
|
|
end
|
|
| Some _ -> false :: acc_res, acc_exn
|
|
end ([], None) konts
|
|
in match opt_exn with
|
|
| Some(exn) -> Some(List.rev res, exn)
|
|
| None -> None
|
|
else
|
|
My_unix.execute_many ~ticker ?max_jobs ~display konts
|
|
end
|
|
end
|
|
;;
|
|
|
|
let execute ?quiet ?pretend cmd =
|
|
match execute_many ?quiet ?pretend [cmd] with
|
|
| Some(_, exn) -> raise exn
|
|
| _ -> ()
|
|
|
|
let iter_tags f x =
|
|
let rec spec x =
|
|
match x with
|
|
| N | A _ | Sh _ | P _ | Px _ | V _ | Quote _ -> ()
|
|
| S l -> List.iter spec l
|
|
| T tags -> f tags
|
|
in
|
|
let rec cmd x =
|
|
match x with
|
|
| Nop | Echo _ -> ()
|
|
| Cmd(s) -> spec s
|
|
| Seq(s) -> List.iter cmd s in
|
|
cmd x
|
|
|
|
let fold_pathnames f x =
|
|
let rec spec = function
|
|
| N | A _ | Sh _ | V _ | Quote _ | T _ -> fun acc -> acc
|
|
| P p | Px p -> f p
|
|
| S l -> List.fold_right spec l
|
|
in
|
|
let rec cmd = function
|
|
| Nop -> fun acc -> acc
|
|
| Echo(_, p) -> f p
|
|
| Cmd(s) -> spec s
|
|
| Seq(s) -> List.fold_right cmd s in
|
|
cmd x
|
|
|
|
let rec reduce x =
|
|
let rec self x acc =
|
|
match x with
|
|
| N -> acc
|
|
| A _ | Sh _ | P _ | Px _ | V _ -> x :: acc
|
|
| S l -> List.fold_right self l acc
|
|
| T tags -> self (!tag_handler tags) acc
|
|
| Quote s -> Quote (reduce s) :: acc in
|
|
match self x [] with
|
|
| [] -> N
|
|
| [x] -> x
|
|
| xs -> S xs
|
|
|
|
let digest =
|
|
let list = List.fold_right in
|
|
let text x acc = Digest.string x :: acc in
|
|
let rec cmd =
|
|
function
|
|
| Cmd spec -> fun acc -> string_of_command_spec spec :: acc
|
|
| Seq seq -> list cmd seq
|
|
| Nop -> fun acc -> acc
|
|
| Echo(texts, dest_path) -> list text (dest_path :: texts)
|
|
in
|
|
fun x ->
|
|
match cmd x [] with
|
|
| [x] -> x
|
|
| xs -> Digest.string ("["^String.concat ";" xs^"]")
|
|
|
|
let all_deps_of_tags = ref []
|
|
|
|
let cons deps acc =
|
|
List.rev&
|
|
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 pdep tags ptag deps =
|
|
Param_tags.declare ptag
|
|
(fun param -> dep (Param_tags.make ptag param :: tags) (deps param))
|
|
|
|
(*
|
|
let to_string_for_digest x =
|
|
let rec cmd_of_spec =
|
|
function
|
|
| [] -> None
|
|
| N :: xs -> cmd_of_spec xs
|
|
| (A x | P x | P x) :: _ -> Some x
|
|
| Sh x :: _ ->
|
|
if Shell.is_simple_filename x then Some x
|
|
else None (* Sh"ocamlfind ocamlc" for example will not be digested. *)
|
|
| S specs1 :: specs2 -> cmd_of_spec (specs1 @ specs2)
|
|
| (T _ | Quote _) :: _ -> assert false in
|
|
let rec cmd_of_cmds =
|
|
function
|
|
| Nop | Seq [] -> None
|
|
| Cmd spec -> cmd_of_spec [spec]
|
|
| Seq (cmd :: _) -> cmd_of_cmds cmd in
|
|
let s = to_string x in
|
|
match cmd_of_cmds x with
|
|
| Some x ->
|
|
if sys_file_exists x then sprintf "(%S,%S)" s (Digest.file x)
|
|
else s
|
|
| None -> s
|
|
*)
|