diff --git a/infer/src/integration/Buck.ml b/infer/src/integration/Buck.ml index 8266a8c37..04b3a3cd4 100644 --- a/infer/src/integration/Buck.ml +++ b/infer/src/integration/Buck.ml @@ -340,20 +340,6 @@ let parse_command_and_targets (buck_mode : BuckMode.t) ~filter_kind original_buc (command, parsed_args.rev_not_targets', targets) -type flavored_arguments = {command: string; rev_not_targets: string list; targets: string list} - -let add_flavors_to_buck_arguments buck_mode ~filter_kind ~extra_flavors original_buck_args = - let command, rev_not_targets, targets = - parse_command_and_targets buck_mode ~filter_kind original_buck_args - in - let targets = - List.rev_map targets ~f:(fun t -> - Target.(t |> of_string |> add_flavor ~extra_flavors buck_mode Config.command |> to_string) - ) - in - {command; rev_not_targets; targets} - - let rec exceed_length ~max = function | _ when max < 0 -> true @@ -380,109 +366,3 @@ let filter_compatible subcommand args = List.filter args ~f:(fun arg -> not (String.equal blacklist arg)) | _ -> args - - -let capture_buck_args = - let clang_path = - List.fold ["clang"; "install"; "bin"; "clang"] ~init:Config.fcp_dir ~f:Filename.concat - in - List.append - [ "--show-output" - ; "--config" - ; "client.id=infer.clang" - ; "--config" - ; Printf.sprintf "*//infer.infer_bin=%s" Config.bin_dir - ; "--config" - ; Printf.sprintf "*//infer.clang_compiler=%s" clang_path - ; "--config" - ; Printf.sprintf "*//infer.clang_plugin=%s" Config.clang_plugin_path - ; "--config" - ; "*//cxx.pch_enabled=false" - ; "--config" - ; (* Infer doesn't support C++ modules yet (T35656509) *) - "*//cxx.modules_default=false" - ; "--config" - ; "*//cxx.modules=false" ] - ( ( match Config.xcode_developer_dir with - | Some d -> - ["--config"; Printf.sprintf "apple.xcode_developer_dir=%s" d] - | None -> - [] ) - @ (if Config.keep_going then ["--keep-going"] else []) - @ ["-j"; Int.to_string Config.jobs] - @ (match Config.load_average with Some l -> ["-L"; Float.to_string l] | None -> []) - @ List.rev_append Config.buck_build_args - ( if not (List.is_empty Config.buck_blacklist) then - [ "--config" - ; Printf.sprintf "*//infer.blacklist_regex=(%s)" - (String.concat ~sep:")|(" Config.buck_blacklist) ] - else [] ) ) - - -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 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 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 - *) - let process_buck_line acc line = - L.debug Capture Verbose "BUCK OUT: %s@." line ; - match String.split ~on:' ' line with - | [_; target_path] -> - let filename = - ResultsDirEntryName.get_path - ~results_dir:(Config.project_root ^/ target_path) - BuckDependencies - in - if PolyVariantEqual.(Sys.file_exists filename = `Yes) then filename :: acc else acc - | _ -> - L.internal_error "Couldn't parse buck target output: %s" line ; - acc - in - List.fold lines ~init:[] ~f:process_buck_line - - -let merge_deps_files depsfiles = - let buck_out = Config.project_root ^/ Config.buck_out_gen in - let depslines, depsfiles = - match depsfiles with - | [] when Config.keep_going || Config.buck_merge_all_deps -> - let infouts = - Utils.fold_folders ~init:[] ~path:buck_out ~f:(fun acc dir -> - if - String.is_substring dir ~substring:"infer-out" - && PolyVariantEqual.( - Sys.file_exists (ResultsDirEntryName.get_path ~results_dir:dir CaptureDB) - = `Yes) - then Printf.sprintf "\t\t%s" dir :: acc - else acc ) - in - (infouts, []) - | [] when Config.buck_merge_all_deps -> - let files = - Utils.find_files ~path:buck_out ~extension:ResultsDirEntryName.buck_infer_deps_file_name - in - ([], files) - | _ -> - ([], depsfiles) - in - depslines - @ List.fold depsfiles ~init:[] ~f:(fun acc file -> - List.rev_append acc (Utils.with_file_in file ~f:In_channel.input_lines) ) - |> List.dedup_and_sort ~compare:String.compare - - -let clang_flavor_capture ~prog ~buck_build_cmd = - if Config.keep_going && not Config.continue_capture then - Process.create_process_and_wait ~prog ~args:["clean"] ; - let depsfiles = run_buck_build prog (buck_build_cmd @ capture_buck_args) in - let deplines = merge_deps_files depsfiles in - let infer_out_depsfile = ResultsDir.get_path BuckDependencies in - Utils.with_file_out infer_out_depsfile ~f:(fun out_chan -> - Out_channel.output_lines out_chan deplines ) ; - () diff --git a/infer/src/integration/Buck.mli b/infer/src/integration/Buck.mli index 11342705c..8660d6372 100644 --- a/infer/src/integration/Buck.mli +++ b/infer/src/integration/Buck.mli @@ -7,6 +7,16 @@ open! IStd +module Target : sig + type t + + val of_string : string -> t + + val to_string : t -> string + + val add_flavor : BuckMode.t -> InferCommand.t -> extra_flavors:string list -> t -> t +end + val wrap_buck_call : ?extend_env:(string * string) list -> label:string -> string list -> string list (** Wrap a call to buck while (i) logging standard error to our standard error in real time; (ii) @@ -27,23 +37,8 @@ val parse_command_and_targets : -> string list -> string * string list * string list -type flavored_arguments = {command: string; rev_not_targets: string list; targets: string list} - -val add_flavors_to_buck_arguments : - BuckMode.t - -> filter_kind:[< `Yes | `No | `Auto] - -> extra_flavors:string list - -> string list - -> flavored_arguments -(** Add infer flavors to the targets in the given buck arguments, depending on the infer analyzer. - For instance, in capture mode, the buck command: build //foo/bar:baz#some,flavor becomes: build - //foo/bar:baz#infer-capture-all,some,flavor *) - val store_args_in_file : string list -> string list (** Given a list of arguments, stores them in a file if needed and returns the new command line *) val filter_compatible : [> `Targets] -> string list -> string list (** keep only the options compatible with the given Buck subcommand *) - -val clang_flavor_capture : prog:string -> buck_build_cmd:string list -> unit -(** do a buck/clang flavor capture given the prog and build command (buck args) *) diff --git a/infer/src/integration/BuckFlavors.ml b/infer/src/integration/BuckFlavors.ml new file mode 100644 index 000000000..59493dbc5 --- /dev/null +++ b/infer/src/integration/BuckFlavors.ml @@ -0,0 +1,154 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) +open! IStd +module CLOpt = CommandLineOption +module L = Logging +module F = Format + +type flavored_arguments = {command: string; rev_not_targets: string list; targets: string list} + +let add_flavors_to_buck_arguments buck_mode ~filter_kind ~extra_flavors original_buck_args = + let command, rev_not_targets, targets = + Buck.parse_command_and_targets buck_mode ~filter_kind original_buck_args + in + let targets = + List.rev_map targets ~f:(fun t -> + Buck.Target.( + t |> of_string |> add_flavor ~extra_flavors buck_mode Config.command |> to_string) ) + in + {command; rev_not_targets; targets} + + +let capture_buck_args = + let clang_path = + List.fold ["clang"; "install"; "bin"; "clang"] ~init:Config.fcp_dir ~f:Filename.concat + in + List.append + [ "--show-output" + ; "--config" + ; "client.id=infer.clang" + ; "--config" + ; Printf.sprintf "*//infer.infer_bin=%s" Config.bin_dir + ; "--config" + ; Printf.sprintf "*//infer.clang_compiler=%s" clang_path + ; "--config" + ; Printf.sprintf "*//infer.clang_plugin=%s" Config.clang_plugin_path + ; "--config" + ; "*//cxx.pch_enabled=false" + ; "--config" + ; (* Infer doesn't support C++ modules yet (T35656509) *) + "*//cxx.modules_default=false" + ; "--config" + ; "*//cxx.modules=false" ] + ( ( match Config.xcode_developer_dir with + | Some d -> + ["--config"; Printf.sprintf "apple.xcode_developer_dir=%s" d] + | None -> + [] ) + @ (if Config.keep_going then ["--keep-going"] else []) + @ ["-j"; Int.to_string Config.jobs] + @ (match Config.load_average with Some l -> ["-L"; Float.to_string l] | None -> []) + @ List.rev_append Config.buck_build_args + ( if not (List.is_empty Config.buck_blacklist) then + [ "--config" + ; Printf.sprintf "*//infer.blacklist_regex=(%s)" + (String.concat ~sep:")|(" Config.buck_blacklist) ] + else [] ) ) + + +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 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 extend_env = [(CommandLineOption.args_env_var, infer_args)] in + let lines = Buck.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 + *) + let process_buck_line acc line = + L.debug Capture Verbose "BUCK OUT: %s@." line ; + match String.split ~on:' ' line with + | [_; target_path] -> + let filename = + ResultsDirEntryName.get_path + ~results_dir:(Config.project_root ^/ target_path) + BuckDependencies + in + if PolyVariantEqual.(Sys.file_exists filename = `Yes) then filename :: acc else acc + | _ -> + L.internal_error "Couldn't parse buck target output: %s" line ; + acc + in + List.fold lines ~init:[] ~f:process_buck_line + + +let merge_deps_files depsfiles = + let buck_out = Config.project_root ^/ Config.buck_out_gen in + let depslines, depsfiles = + match depsfiles with + | [] when Config.keep_going || Config.buck_merge_all_deps -> + let infouts = + Utils.fold_folders ~init:[] ~path:buck_out ~f:(fun acc dir -> + if + String.is_substring dir ~substring:"infer-out" + && PolyVariantEqual.( + Sys.file_exists (ResultsDirEntryName.get_path ~results_dir:dir CaptureDB) + = `Yes) + then Printf.sprintf "\t\t%s" dir :: acc + else acc ) + in + (infouts, []) + | [] when Config.buck_merge_all_deps -> + let files = + Utils.find_files ~path:buck_out ~extension:ResultsDirEntryName.buck_infer_deps_file_name + in + ([], files) + | _ -> + ([], depsfiles) + in + depslines + @ List.fold depsfiles ~init:[] ~f:(fun acc file -> + List.rev_append acc (Utils.with_file_in file ~f:In_channel.input_lines) ) + |> List.dedup_and_sort ~compare:String.compare + + +let clang_flavor_capture ~prog ~buck_build_cmd = + if Config.keep_going && not Config.continue_capture then + Process.create_process_and_wait ~prog ~args:["clean"] ; + let depsfiles = run_buck_build prog (buck_build_cmd @ capture_buck_args) in + let deplines = merge_deps_files depsfiles in + let infer_out_depsfile = ResultsDir.get_path BuckDependencies in + Utils.with_file_out infer_out_depsfile ~f:(fun out_chan -> + Out_channel.output_lines out_chan deplines ) ; + () + + +let capture build_cmd = + let prog, buck_args = (List.hd_exn build_cmd, List.tl_exn build_cmd) in + (* let children infer processes know that they are inside Buck *) + let infer_args_with_buck = + String.concat + ~sep:(String.of_char CLOpt.env_var_sep) + (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--buck"]) + in + Unix.putenv ~key:CLOpt.args_env_var ~data:infer_args_with_buck ; + let {command; rev_not_targets; targets} = + add_flavors_to_buck_arguments ClangFlavors ~filter_kind:`Auto ~extra_flavors:[] buck_args + in + if List.is_empty targets then () + else + let all_args = List.rev_append rev_not_targets targets in + let updated_buck_cmd = + command :: List.rev_append Config.buck_build_args_no_inline (Buck.store_args_in_file all_args) + in + L.debug Capture Quiet "Processed buck command '%a'@\n" (Pp.seq F.pp_print_string) + updated_buck_cmd ; + let prog, buck_build_cmd = (prog, updated_buck_cmd) in + ResultsDir.RunState.set_merge_capture true ; + clang_flavor_capture ~prog ~buck_build_cmd diff --git a/infer/src/integration/BuckFlavors.mli b/infer/src/integration/BuckFlavors.mli new file mode 100644 index 000000000..8a0a03c58 --- /dev/null +++ b/infer/src/integration/BuckFlavors.mli @@ -0,0 +1,23 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open! IStd + +type flavored_arguments = {command: string; rev_not_targets: string list; targets: string list} + +val add_flavors_to_buck_arguments : + BuckMode.t + -> filter_kind:[< `Yes | `No | `Auto] + -> extra_flavors:string list + -> string list + -> flavored_arguments +(** Add infer flavors to the targets in the given buck arguments, depending on the infer analyzer. + For instance, in capture mode, the buck command: build //foo/bar:baz#some,flavor becomes: build + //foo/bar:baz#infer-capture-all,some,flavor *) + +val capture : string list -> unit +(** do a buck/clang flavor capture given the prog and build command (buck args) *) diff --git a/infer/src/integration/CaptureCompilationDatabase.ml b/infer/src/integration/CaptureCompilationDatabase.ml index 618fbfef7..ae3b21d95 100644 --- a/infer/src/integration/CaptureCompilationDatabase.ml +++ b/infer/src/integration/CaptureCompilationDatabase.ml @@ -70,7 +70,7 @@ let run_compilation_database compilation_database should_capture_file = (** Computes the compilation database files. *) let get_compilation_database_files_buck db_deps ~prog ~args = match - Buck.add_flavors_to_buck_arguments (ClangCompilationDB db_deps) ~filter_kind:`Yes + BuckFlavors.add_flavors_to_buck_arguments (ClangCompilationDB db_deps) ~filter_kind:`Yes ~extra_flavors:Config.append_buck_flavors args with | {targets} when List.is_empty targets -> diff --git a/infer/src/integration/Driver.ml b/infer/src/integration/Driver.ml index 6ca0eda5b..680bcb8f0 100644 --- a/infer/src/integration/Driver.ml +++ b/infer/src/integration/Driver.ml @@ -110,42 +110,6 @@ let capture_with_compilation_database db_files = CaptureCompilationDatabase.capture_files_in_database compilation_database -let buck_capture build_cmd = - let prog_build_cmd_opt = - let prog, buck_args = (List.hd_exn build_cmd, List.tl_exn build_cmd) in - match Config.buck_mode with - | Some ClangFlavors -> - (* let children infer processes know that they are inside Buck *) - let infer_args_with_buck = - String.concat - ~sep:(String.of_char CLOpt.env_var_sep) - (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--buck"]) - in - Unix.putenv ~key:CLOpt.args_env_var ~data:infer_args_with_buck ; - let {Buck.command; rev_not_targets; targets} = - Buck.add_flavors_to_buck_arguments ClangFlavors ~filter_kind:`Auto ~extra_flavors:[] - buck_args - in - if List.is_empty targets then None - else - let all_args = List.rev_append rev_not_targets targets in - let updated_buck_cmd = - command - :: List.rev_append Config.buck_build_args_no_inline (Buck.store_args_in_file all_args) - in - Logging.(debug Capture Quiet) - "Processed buck command '%a'@\n" (Pp.seq F.pp_print_string) updated_buck_cmd ; - Some (prog, updated_buck_cmd) - | _ -> - Some (prog, build_cmd) - in - Option.iter prog_build_cmd_opt ~f:(fun (prog, buck_build_cmd) -> - L.progress "Capturing in buck mode...@." ; - if Option.exists ~f:BuckMode.is_clang_flavors Config.buck_mode then - ResultsDir.RunState.set_merge_capture true ; - Buck.clang_flavor_capture ~prog ~buck_build_cmd ) - - let capture ~changed_files = function | Analyze -> () @@ -153,7 +117,8 @@ let capture ~changed_files = function L.progress "Capturing in ant mode...@." ; Ant.capture ~prog ~args | BuckClangFlavor {build_cmd} -> - buck_capture build_cmd + L.progress "Capturing in buck mode...@." ; + BuckFlavors.capture build_cmd | BuckCompilationDB {deps; prog; args} -> L.progress "Capturing using Buck's compilation database...@." ; let json_cdb =