diff --git a/infer/src/integration/Buck.ml b/infer/src/integration/Buck.ml index 93ffc234a..8266a8c37 100644 --- a/infer/src/integration/Buck.ml +++ b/infer/src/integration/Buck.ml @@ -9,6 +9,50 @@ open! IStd module F = Format module L = Logging +(** Wrap a call to buck while (i) logging standard error to our standard error in real time; (ii) + redirecting standard out to a file, the contents of which are returned; (iii) protect the child + process from [SIGQUIT]. + + We want to ihnibit [SIGQUIT] because the standard action of the JVM is to print a thread dump on + stdout, polluting the output we want to collect (and normally does not lead to process death). + To achieve this we need to do two things: (i) tell the JVM not to use signals, meaning it leaves + the default handler for [SIGQUIT] in place; (ii) uninstall the default handler for [SIGQUIT] + because now that the JVM doesn't touch it, it will lead to process death. *) +let wrap_buck_call ?(extend_env = []) ~label cmd = + let stdout_file = + let prefix = Printf.sprintf "buck_%s" label in + Filename.temp_file ~in_dir:(ResultsDir.get_path Temporary) prefix ".stdout" + in + let escaped_cmd = List.map ~f:Escape.escape_shell cmd |> String.concat ~sep:" " in + let sigquit_protected_cmd = + (* Uninstall the default handler for [SIGQUIT]. *) + Printf.sprintf "trap '' SIGQUIT ; exec %s >'%s'" escaped_cmd stdout_file + in + let env = + (* Instruct the JVM to avoid using signals. *) + `Extend (("BUCK_EXTRA_JAVA_ARGS", "-Xrs") :: extend_env) + in + let Unix.Process_info.{stdin; stdout; stderr; pid} = + Unix.create_process_env ~prog:"sh" ~args:["-c"; sigquit_protected_cmd] ~env () + in + let buck_stderr = Unix.in_channel_of_descr stderr in + Utils.with_channel_in buck_stderr ~f:(L.progress "BUCK: %s@.") ; + Unix.close stdin ; + Unix.close stdout ; + In_channel.close buck_stderr ; + match Unix.waitpid pid with + | Ok () -> ( + match Utils.read_file stdout_file with + | Ok lines -> + lines + | Error err -> + L.die ExternalError "*** failed to read output of buck command %s: %s" sigquit_protected_cmd + err ) + | Error _ as err -> + L.die ExternalError "*** failed to execute buck command %s: %s" sigquit_protected_cmd + (Unix.Exit_or_signal.to_string_hum err) + + module Target = struct type t = {name: string; flavors: string list} @@ -121,9 +165,7 @@ module Query = struct let cmd = ("buck" :: "query" :: buck_config) @ List.rev_append Config.buck_build_args_no_inline [query] in - let tmp_prefix = "buck_query_" in - let debug = L.(debug Capture Medium) in - Utils.with_process_lines ~debug ~cmd ~tmp_prefix ~f:Fn.id + wrap_buck_call ~label:"query" cmd end let accepted_buck_commands = ["build"] @@ -194,15 +236,13 @@ let resolve_pattern_targets (buck_mode : BuckMode.t) ~filter_kind targets = let resolve_alias_targets aliases = - let debug = L.(debug Capture Medium) in (* we could use buck query to resolve aliases but buck targets --resolve-alias is faster *) let cmd = "buck" :: "targets" :: "--resolve-alias" :: aliases in - let tmp_prefix = "buck_targets_" in let on_result_lines = die_if_empty (fun die -> die "*** No alias found for: '%a'." (Pp.seq ~sep:"', '" F.pp_print_string) aliases ) in - Utils.with_process_lines ~debug ~cmd ~tmp_prefix ~f:on_result_lines + wrap_buck_call ~label:"targets" cmd |> on_result_lines type parsed_args = @@ -381,27 +421,12 @@ let capture_buck_args = let run_buck_build prog buck_build_args = L.debug Capture Verbose "%s %s@." prog (List.to_string ~f:Fn.id buck_build_args) ; - let buck_output_file = - Filename.temp_file ~in_dir:(ResultsDir.get_path Temporary) "buck_output" ".log" - in let infer_args = Option.fold (Sys.getenv CommandLineOption.args_env_var) ~init:"--fcp-syntax-only" ~f:(fun acc arg -> Printf.sprintf "%s%c%s" acc CommandLineOption.env_var_sep arg) in - let shell_cmd = - List.map ~f:Escape.escape_shell (prog :: buck_build_args) - |> String.concat ~sep:" " - |> fun cmd -> Printf.sprintf "%s >'%s'" cmd buck_output_file - in - let env = `Extend [(CommandLineOption.args_env_var, infer_args)] in - let {Unix.Process_info.stdin; stdout; stderr; pid} = - Unix.create_process_env ~prog:"sh" ~args:["-c"; shell_cmd] ~env () - in - let buck_stderr = Unix.in_channel_of_descr stderr in - Utils.with_channel_in buck_stderr ~f:(L.progress "BUCK: %s@.") ; - Unix.close stdin ; - Unix.close stdout ; - In_channel.close buck_stderr ; + let extend_env = [(CommandLineOption.args_env_var, infer_args)] in + let lines = wrap_buck_call ~extend_env ~label:"build" (prog :: buck_build_args) in (* Process a line of buck stdout output, in this case the result of '--show-output' These paths (may) contain a 'infer-deps.txt' file, which we will later merge *) @@ -419,16 +444,7 @@ let run_buck_build prog buck_build_args = L.internal_error "Couldn't parse buck target output: %s" line ; acc in - match Unix.waitpid pid with - | Ok () -> ( - match Utils.read_file buck_output_file with - | Ok lines -> - List.fold lines ~init:[] ~f:process_buck_line - | Error err -> - L.die ExternalError "*** capture failed to execute: %s" err ) - | Error _ as err -> - L.die ExternalError "*** capture failed to execute: %s" - (Unix.Exit_or_signal.to_string_hum err) + List.fold lines ~init:[] ~f:process_buck_line let merge_deps_files depsfiles =