[driver] better user error messages

Summary:
- Do not print OCaml backtrace unless in developer mode.
- Print javac output when it fails.
- Refactor Javac.ml so that rerunning the command doesn't duplicate code.

The output of the clang and Javac integration is good, the output for mvn is
ok, the output for Buck is still fairly "meh".

Reviewed By: jberdine

Differential Revision: D4818722

fbshipit-source-id: 1973b93
master
Jules Villard 8 years ago committed by Facebook Github Bot
parent 142fe31fae
commit cccefd6434

@ -1499,7 +1499,16 @@ let post_parsing_initialization () =
); );
if !version <> `None then exit 0; if !version <> `None then exit 0;
if !developer_mode then Printexc.record_backtrace true ; (* Core sets a verbose exception handler by default, with backtrace. This is good for developers
but in user-mode we want something lighter weight. *)
if not !developer_mode then
Caml.Printexc.set_uncaught_exception_handler
(fun exn _ ->
let exn_msg = match exn with
| Failure msg -> msg
| _ -> "ERROR: " ^ Caml.Printexc.to_string exn in
Format.eprintf "%s@?" exn_msg
);
F.set_margin !margin ; F.set_margin !margin ;

@ -205,14 +205,20 @@ let do_finally f g =
let res' = g () in let res' = g () in
(res, res') (res, res')
let with_file file ~f = let with_file_in file ~f =
let oc = open_out file in let ic = In_channel.create file in
let f () = f ic in
let g () = In_channel.close ic in
do_finally f g |> fst
let with_file_out file ~f =
let oc = Out_channel.create file in
let f () = f oc in let f () = f oc in
let g () = Out_channel.close oc in let g () = Out_channel.close oc in
do_finally f g |> fst do_finally f g |> fst
let write_json_to_file destfile json = let write_json_to_file destfile json =
with_file destfile ~f:(fun oc -> Yojson.Basic.pretty_to_channel oc json) with_file_out destfile ~f:(fun oc -> Yojson.Basic.pretty_to_channel oc json)
let consume_in chan_in = let consume_in chan_in =
try try
@ -227,6 +233,18 @@ let with_process_in command read =
Unix.close_process_in chan in Unix.close_process_in chan in
do_finally f g do_finally f g
let shell_escape_command cmd =
let escape arg =
(* ends on-going single quote, output single quote inside double quotes, then open a new single
quote *)
Escape.escape_map (function | '\'' -> Some "'\"'\"'" | _ -> None) arg
|> Printf.sprintf "'%s'" in
List.map ~f:escape cmd |> String.concat ~sep:" "
let run_command_and_get_output cmd =
let shell_escaped_cmd = shell_escape_command cmd in
with_process_in (Printf.sprintf "%s 2>&1" shell_escaped_cmd) In_channel.input_lines
(** Create a directory if it does not exist already. *) (** Create a directory if it does not exist already. *)
let create_dir dir = let create_dir dir =
try try

@ -57,13 +57,16 @@ val dir_is_empty : string -> bool
val read_optional_json_file : string -> (Yojson.Basic.json, string) Result.t val read_optional_json_file : string -> (Yojson.Basic.json, string) Result.t
val with_file : string -> f:(out_channel -> 'a) -> 'a val with_file_in : string -> f:(In_channel.t -> 'a) -> 'a
val with_file_out : string -> f:(Out_channel.t -> 'a) -> 'a
val write_json_to_file : string -> Yojson.Basic.json -> unit val write_json_to_file : string -> Yojson.Basic.json -> unit
val consume_in : in_channel -> unit val consume_in : in_channel -> unit
val with_process_in: string -> (in_channel -> 'a) -> ('a * Unix.Exit_or_signal.t) val with_process_in : string -> (in_channel -> 'a) -> ('a * Unix.Exit_or_signal.t)
val shell_escape_command : string list -> string
(** create a directory if it does not exist already *) (** create a directory if it does not exist already *)
val create_dir : string -> unit val create_dir : string -> unit

@ -87,12 +87,12 @@ let normalize prog::prog args::args :list action_item => {
let exec_action_item = let exec_action_item =
fun fun
| ClangError error => { | ClangError error =>
/* An error in the output of `clang -### ...`. Outputs the error and fail. This is because /* An error in the output of `clang -### ...`. Outputs the error and fail. This is because
`clang -###` pretty much never fails, but warns of failures on stderr instead. */ `clang -###` pretty much never fails, but warns of failures on stderr instead. */
Logging.stderr "%s@." error; failwithf
exit 1 "@\n*** ERROR: Failed to execute compilation command. Output:@\n%s@\n*** Infer needs a working compilation command to run.@."
} error
| ClangWarning warning => Logging.stderr "%s@\n" warning | ClangWarning warning => Logging.stderr "%s@\n" warning
| Command clang_cmd => Capture.capture clang_cmd; | Command clang_cmd => Capture.capture clang_cmd;

@ -313,7 +313,7 @@ let save_dotty_when_in_debug_mode source_file =
let source_file_basename = Filename.basename (SourceFile.to_abs_path source_file) in let source_file_basename = Filename.basename (SourceFile.to_abs_path source_file) in
let file = dotty_dir ^/ (source_file_basename ^ ".dot") in let file = dotty_dir ^/ (source_file_basename ^ ".dot") in
let dotty = Debug.EvaluationTracker.DottyPrinter.dotty_of_ctl_evaluation !tracker in let dotty = Debug.EvaluationTracker.DottyPrinter.dotty_of_ctl_evaluation !tracker in
Utils.with_file file ~f:(fun oc -> output_string oc dotty) Utils.with_file_out file ~f:(fun oc -> output_string oc dotty)
| _ -> () | _ -> ()
(* Helper functions *) (* Helper functions *)

@ -87,8 +87,7 @@ let get_compilation_database_files_buck () =
Process.create_process_and_wait ~prog:buck ~args:build_args; Process.create_process_and_wait ~prog:buck ~args:build_args;
let buck_targets_shell = let buck_targets_shell =
buck :: "targets" :: "--show-output" :: args_with_flavor buck :: "targets" :: "--show-output" :: args_with_flavor
|> List.map ~f:(Printf.sprintf "'%s'") |> Utils.shell_escape_command in
|> String.concat ~sep:" " in
try try
match fst @@ Utils.with_process_in buck_targets_shell In_channel.input_lines with match fst @@ Utils.with_process_in buck_targets_shell In_channel.input_lines with
| [] -> Logging.stdout "There are no files to process, exiting."; exit 0 | [] -> Logging.stdout "There are no files to process, exiting."; exit 0
@ -107,8 +106,7 @@ let get_compilation_database_files_buck () =
List.fold ~f:scan_output ~init:[] lines List.fold ~f:scan_output ~init:[] lines
with Unix.Unix_error (err, _, _) -> with Unix.Unix_error (err, _, _) ->
Process.print_error_and_exit Process.print_error_and_exit
"Cannot execute %s\n%!" "Cannot execute %s: %s\n%!" buck_targets_shell (Unix.error_message err)
(buck_targets_shell ^ " " ^ (Unix.error_message err))
) )
| _ -> | _ ->
let cmd = String.concat ~sep:" " cmd in let cmd = String.concat ~sep:" " cmd in

@ -36,7 +36,7 @@ let mk_arg_file prefix style args => {
let file = Filename.temp_file in_dir::temp_dir prefix ".txt"; let file = Filename.temp_file in_dir::temp_dir prefix ".txt";
let write_args outc => let write_args outc =>
output_string outc (List.map f::(quote style) args |> String.concat sep::" "); output_string outc (List.map f::(quote style) args |> String.concat sep::" ");
Utils.with_file file f::write_args |> ignore; Utils.with_file_out file f::write_args |> ignore;
Logging.out "Clang options stored in file %s@\n" file; Logging.out "Clang options stored in file %s@\n" file;
file file
}; };

@ -59,23 +59,37 @@ let compile compiler build_prog build_args =
file in file in
let cli_file_args = cli_args @ ["@" ^ args_file] in let cli_file_args = cli_args @ ["@" ^ args_file] in
let args = prog_args @ cli_file_args in let args = prog_args @ cli_file_args in
L.out "Current working directory: '%s'@." (Sys.getcwd ());
let verbose_out_file = Filename.temp_file "javac_" ".out" in let verbose_out_file = Filename.temp_file "javac_" ".out" in
Unix.with_file verbose_out_file ~mode:[Unix.O_WRONLY] ~f:( let try_run cmd error_k =
fun verbose_out_fd -> let shell_cmd = Utils.shell_escape_command cmd in
L.out "Logging into %s@\n" verbose_out_file; let shell_cmd_redirected =
L.out "Current working directory: '%s'@." (Sys.getcwd ()); Printf.sprintf "%s 2>'%s'" shell_cmd verbose_out_file in
try L.out "Trying to execute: %s@." shell_cmd_redirected;
L.out "Trying to execute: '%s' '%s'@." prog (String.concat ~sep:"' '" args); let error_k_or_fail err_msg =
Unix_.fork_redirect_exec_wait ~prog ~args ~stderr:verbose_out_fd () match error_k, err_msg with
with exn -> | Some k, (`UnixError (err, log)) ->
try L.out "*** Failed: %s!@\n%s@." (Unix.Exit_or_signal.to_string_hum (Error err)) log;
L.out "*** Failed!@\nTrying to execute javac instead: '%s' '%s'@\nLogging into %s@." k ()
"javac" (String.concat ~sep:"' '" cli_file_args) verbose_out_file; | Some k, (`ExceptionError exn) ->
Unix_.fork_redirect_exec_wait ~prog:"javac" ~args:cli_file_args ~stderr:verbose_out_fd () L.out "*** Failed: %a!@\n" Exn.pp exn;
with _ -> k ()
L.stderr "Failed to execute: %s %s@." prog (String.concat ~sep:" " args); | None, (`UnixError (err, log)) ->
raise exn let verbose_errlog = Utils.with_file_in verbose_out_file ~f:In_channel.input_all in
); failwithf "@\n*** ERROR Failed to execute compilation command: %s@\n*** Command: %s@\n\
*** Output:@\n%s%s@\n*** Infer needs a working compilation command to run.@."
(Unix.Exit_or_signal.to_string_hum (Error err)) shell_cmd log verbose_errlog;
| None, (`ExceptionError exn) ->
raise exn in
match Utils.with_process_in shell_cmd_redirected In_channel.input_all with
| (log, Error err) ->
error_k_or_fail (`UnixError (err, log))
| exception exn ->
error_k_or_fail (`ExceptionError exn)
| (log, Ok ()) ->
L.out "*** Success. Logs:@\n%s" log in
let fallback () = try_run ("javac"::cli_file_args) None in
try_run (prog::args) (Some fallback);
verbose_out_file verbose_out_file

@ -120,7 +120,7 @@ let add_infer_profile mvn_pom infer_pom =
let xml_out = Xmlm.make_output ~nl:true (`Channel out_chan) in let xml_out = Xmlm.make_output ~nl:true (`Channel out_chan) in
add_infer_profile_to_xml (Filename.dirname mvn_pom) xml_in xml_out in add_infer_profile_to_xml (Filename.dirname mvn_pom) xml_in xml_out in
protect ~f:with_ic ~finally:(fun () -> In_channel.close ic) in protect ~f:with_ic ~finally:(fun () -> In_channel.close ic) in
Utils.with_file infer_pom ~f:with_oc Utils.with_file_out infer_pom ~f:with_oc
let add_profile_to_pom_in_directory dir = let add_profile_to_pom_in_directory dir =
(* Even though there is a "-f" command-line arguments to change the config file Maven reads from, (* Even though there is a "-f" command-line arguments to change the config file Maven reads from,

Loading…
Cancel
Save