300 lines
8.6 KiB
OCaml
300 lines
8.6 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. *)
|
|
(* *)
|
|
(***********************************************************************)
|
|
|
|
(* $Id$ *)
|
|
(* Original author: Nicolas Pouillard *)
|
|
(* Command *)
|
|
|
|
open My_std
|
|
open Log
|
|
|
|
type tags = Tags.t
|
|
|
|
let jobs = ref 1
|
|
|
|
type t =
|
|
| Seq of t list
|
|
| Cmd of spec
|
|
| Nop
|
|
and spec =
|
|
| N (* nop or nil *)
|
|
| S of spec list
|
|
| A of string
|
|
| P of string (* Pathname.t *)
|
|
| Px of string (* Pathname.t *)
|
|
| 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 string (* Pathname.t *)
|
|
| `Px of string (* Pathname.t *)
|
|
| `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
|
|
Lexers.colon_sep_strings (Lexing.from_string path_var)
|
|
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)
|
|
|
|
|
|
(* FIXME windows *)
|
|
let search_in_path cmd =
|
|
if Filename.is_implicit cmd then
|
|
let path = List.find begin fun path ->
|
|
if path = Filename.current_dir_name then sys_file_exists cmd
|
|
else sys_file_exists (filename_concat path cmd)
|
|
end !*env_path in
|
|
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
|
|
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 s = string_of_command_spec_with_calls ((:=) rtags) ((:=) rtarget) true spec in
|
|
let target = if !rtarget = "" then s else !rtarget in
|
|
s, target, !rtags
|
|
|
|
let string_print_of_command_spec spec =
|
|
let s, target, tags = string_target_and_tags_of_command_spec spec in
|
|
(s, (fun quiet pretend () -> if not quiet then Log.event ~pretend s target tags))
|
|
(* ***)
|
|
|
|
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"
|
|
|
|
let to_string x = sbprintf "%a" print x
|
|
|
|
let rec list_rev_iter f =
|
|
function
|
|
| [] -> ()
|
|
| x :: xs -> list_rev_iter f xs; f x
|
|
|
|
let spec_list_of_cmd cmd =
|
|
let rec loop acc =
|
|
function
|
|
| [] -> acc
|
|
| Nop :: xs -> loop acc xs
|
|
| Cmd spec :: xs -> loop (string_print_of_command_spec spec :: acc) xs
|
|
| Seq l :: xs -> loop (loop acc l) xs
|
|
in List.rev (loop [] [cmd])
|
|
|
|
let execute_many ?(quiet=false) ?(pretend=false) 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
|
|
begin fun cmd ->
|
|
let specs = spec_list_of_cmd cmd in
|
|
List.map
|
|
begin fun (cmd, print) ->
|
|
(cmd, (print quiet pretend))
|
|
end
|
|
specs
|
|
end
|
|
cmds
|
|
in
|
|
if pretend then
|
|
begin
|
|
List.iter
|
|
begin fun l ->
|
|
List.iter
|
|
begin fun (_, f) -> f () end
|
|
l
|
|
end
|
|
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 (cmd, print) ->
|
|
print ();
|
|
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(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 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 to_string_for_digest = to_string
|
|
(*
|
|
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
|
|
*)
|