1 (***********************************************************************)
4 (* Nicolas Pouillard, Berke Durak, projet Gallium, INRIA Rocquencourt *)
6 (* Copyright 2007 Institut National de Recherche en Informatique et *)
7 (* en Automatique. All rights reserved. This file is distributed *)
8 (* under the terms of the Q Public License version 1.0. *)
10 (***********************************************************************)
13 (* Original author: Nicolas Pouillard *)
20 type pathname
= string
27 | Echo
of string list
* pathname
40 (*type v = [ `Seq of v list | `Cmd of vspec | `Nop ]
50 let rec spec_of_vspec =
53 | `S vspecs -> S (List.map spec_of_vspec vspecs)
58 | `Quote vspec -> Quote (spec_of_vspec vspec)
60 let rec vspec_of_spec =
63 | S specs -> `S (List.map vspec_of_spec specs)
68 | T _ -> invalid_arg "vspec_of_spec: T not supported"
69 | Quote spec -> `Quote (vspec_of_spec spec)
74 | `Cmd vspec -> Cmd (spec_of_vspec vspec)
75 | `Seq cmds -> Seq (List.map t_of_v cmds)
80 | Cmd spec -> `Cmd (vspec_of_spec spec)
81 | Seq cmds -> `Seq (List.map v_of_t cmds)*)
83 let no_tag_handler _
= failwith
"no_tag_handler"
85 let tag_handler = ref no_tag_handler
88 let atomize l
= S
(List.map
(fun x
-> A x
) l
)
89 let atomize_paths l
= S
(List.map
(fun x
-> P x
) l
)
92 let env_path = lazy begin
93 let path_var = Sys.getenv
"PATH" in
94 Lexers.colon_sep_strings
(Lexing.from_string
path_var)
97 let virtual_solvers = Hashtbl.create
32
98 let setup_virtual_command_solver virtual_command solver
=
99 Hashtbl.replace
virtual_solvers virtual_command solver
100 let virtual_solver virtual_command
=
103 Hashtbl.find
virtual_solvers virtual_command
105 failwith
(sbprintf
"no solver for the virtual command %S \
106 (setup one with Command.setup_virtual_command_solver)"
111 failwith
(Printf.sprintf
"the solver for the virtual command %S \
112 has failed finding a valid command" virtual_command
)
116 let search_in_path cmd
=
117 if Filename.is_implicit cmd
then
118 let path = List.find
begin fun path ->
119 if path = Filename.current_dir_name
then sys_file_exists cmd
120 else sys_file_exists
(filename_concat
path cmd
)
122 filename_concat
path cmd
125 (*** string_of_command_spec{,_with_calls *)
126 let rec string_of_command_spec_with_calls call_with_tags call_with_target resolve_virtuals spec
=
127 let self = string_of_command_spec_with_calls call_with_tags call_with_target resolve_virtuals
in
128 let b = Buffer.create
256 in
129 let first = ref true in
134 Buffer.add_char
b ' '
137 Buffer.add_string
b (Shell.quote_filename_if_needed p
)
139 let rec do_spec = function
141 | A u
-> put_space (); put_filename u
142 | Sh u
-> put_space (); Buffer.add_string
b u
143 | P p
-> put_space (); put_filename p
144 | Px u
-> put_space (); put_filename u
; call_with_target u
145 | V v
-> if resolve_virtuals
then do_spec (virtual_solver v
)
146 else (put_space (); Printf.bprintf
b "<virtual %s>" (Shell.quote_filename_if_needed v
))
147 | S l
-> List.iter
do_spec l
148 | T tags
-> call_with_tags tags
; do_spec (!tag_handler tags
)
149 | Quote s
-> put_space (); put_filename (self s
)
154 let string_of_command_spec x
= string_of_command_spec_with_calls ignore ignore
false x
156 let string_target_and_tags_of_command_spec spec
=
157 let rtags = ref Tags.empty
in
158 let rtarget = ref "" in
159 let s = string_of_command_spec_with_calls ((:=) rtags) ((:=) rtarget) true spec
in
160 let target = if !rtarget = "" then s else !rtarget in
163 let string_print_of_command_spec spec quiet pretend
=
164 let s, target, tags
= string_target_and_tags_of_command_spec spec
in
165 fun () -> if not quiet
then Log.event ~pretend
s target tags
; s
168 let print_escaped_string f
= Format.fprintf f
"%S"
172 | Cmd spec
-> Format.pp_print_string f
(string_of_command_spec spec
)
173 | Seq seq
-> List.print print f seq
174 | Nop
-> Format.pp_print_string f
"nop"
175 | Echo
(texts
, dest_path
) ->
176 Format.fprintf f
"@[<2>Echo(%a,@ %a)@]"
177 (List.print print_escaped_string) texts
print_escaped_string dest_path
179 let to_string x
= sbprintf
"%a" print x
181 let add_parallel_stat, dump_parallel_stats
=
182 let xmin = ref max_int
in
185 let xsumall = ref 0 in
186 let xcount = ref 0 in
187 let xcountall = ref 0 in
188 let add_parallel_stat x
=
191 xsumall := x
+ !xsumall;
200 let dump_parallel_stats () =
203 dprintf
1 "# No parallelism done"
205 let xaverage = float_of_int
!xsumall /. float_of_int
!xcountall in
206 let xaveragepara = float_of_int
!xsum /. float_of_int
!xcount in
207 dprintf
1 "# Parallel statistics: { count(total): %d(%d), max: %d, min: %d, average(total): %.3f(%.3f) }"
208 !xcount !xcountall !xmax !xmin xaveragepara xaverage
210 add_parallel_stat, dump_parallel_stats
212 module Primitives
= struct
213 let do_echo texts dest_path
=
214 with_output_file dest_path
begin fun oc
->
215 List.iter
(output_string oc
) texts
217 let echo x y
() = (* no print here yet *) do_echo x y
; ""
220 let rec list_rev_iter f
=
223 | x
:: xs
-> list_rev_iter f xs
; f x
225 let flatten_commands quiet pretend cmd
=
229 | Nop
:: xs
-> loop acc xs
230 | Cmd spec
:: xs
-> loop (string_print_of_command_spec spec quiet pretend
:: acc
) xs
231 | Echo
(texts
, dest_path
) :: xs
-> loop (Primitives.echo texts dest_path
:: acc
) xs
232 | Seq l
:: xs
-> loop (loop acc l
) xs
233 in List.rev
(loop [] [cmd
])
235 let execute_many ?
(quiet
=false) ?
(pretend
=false) cmds
=
236 add_parallel_stat (List.length cmds
);
237 let degraded = !*My_unix.is_degraded
|| Sys.os_type
= "Win32" in
239 if jobs < 0 then invalid_arg
"jobs < 0";
240 let max_jobs = if jobs = 0 then None
else Some
jobs in
242 let ticker = Log.update
in
243 let display = Log.display in
249 let konts = List.map
(flatten_commands quiet pretend
) cmds
in
252 List.iter
(List.iter
(fun f
-> ignore
(f
()))) konts;
257 reset_filesys_cache
();
260 List.fold_left
begin fun (acc_res
, acc_exn
) cmds
->
264 List.iter
begin fun action
->
265 let cmd = action
() in
266 let rc = sys_command
cmd in
267 if rc <> 0 then begin
269 eprintf
"Exit code %d while executing this \
270 command:@\n%s" rc cmd;
271 raise
(Exit_with_code
rc)
274 true :: acc_res
, None
275 with e
-> false :: acc_res
, Some e
277 | Some _
-> false :: acc_res
, acc_exn
279 in match opt_exn
with
280 | Some
(exn
) -> Some
(res, exn
)
283 My_unix.execute_many ~
ticker ?
max_jobs ~
display konts
288 let execute ?quiet ?pretend
cmd =
289 match execute_many ?quiet ?pretend
[cmd] with
290 | Some
(_
, exn
) -> raise exn
296 | N
| A _
| Sh _
| P _
| Px _
| V _
| Quote _
-> ()
297 | S l
-> List.iter
spec l
304 | Seq
(s) -> List.iter
cmd s in
311 | A _
| Sh _
| P _
| Px _
| V _
-> x
:: acc
312 | S l
-> List.fold_right
self l acc
313 | T tags
-> self (!tag_handler tags
) acc
314 | Quote
s -> Quote
(reduce s) :: acc
in
321 let list = List.fold_right
in
322 let text x acc
= Digest.string x
:: acc
in
325 | Cmd
spec -> fun acc
-> string_of_command_spec spec :: acc
326 | Seq seq
-> list cmd seq
327 | Nop
-> fun acc
-> acc
328 | Echo
(texts
, dest_path
) -> list text (dest_path
:: texts
)
333 | xs
-> Digest.string ("["^
String.concat
";" xs^
"]")
336 let to_string_for_digest x =
337 let rec cmd_of_spec =
340 | N :: xs -> cmd_of_spec xs
341 | (A x | P x | P x) :: _ -> Some x
343 if Shell.is_simple_filename x then Some x
344 else None (* Sh"ocamlfind ocamlc" for example will not be digested. *)
345 | S specs1
:: specs2
-> cmd_of_spec (specs1
@ specs2
)
346 | (T _
| Quote _
) :: _
-> assert false in
347 let rec cmd_of_cmds =
349 | Nop
| Seq
[] -> None
350 | Cmd
spec -> cmd_of_spec [spec]
351 | Seq
(cmd :: _
) -> cmd_of_cmds cmd in
352 let s = to_string x
in
353 match cmd_of_cmds x
with
355 if sys_file_exists x
then sprintf
"(%S,%S)" s (Digest.file x
)