From 284c6fdb3b8533c1a1c6fcbddf0849f6bde231a2 Mon Sep 17 00:00:00 2001 From: Martin Trojer Date: Thu, 5 Mar 2020 22:51:44 -0800 Subject: [PATCH] [buck] replace python buck integration with ocaml Reviewed By: jvillard Differential Revision: D20248852 fbshipit-source-id: c842ccadf --- infer/lib/python/inferlib/capture/buck.py | 253 ---------------------- infer/src/base/Config.ml | 8 + infer/src/base/Config.mli | 8 +- infer/src/base/Utils.ml | 19 ++ infer/src/base/Utils.mli | 3 + infer/src/clang/ClangCommand.ml | 13 +- infer/src/integration/Buck.ml | 106 +++++++++ infer/src/integration/Buck.mli | 3 + infer/src/integration/Driver.ml | 125 ++++++----- infer/src/integration/Driver.mli | 1 + 10 files changed, 210 insertions(+), 329 deletions(-) delete mode 100644 infer/lib/python/inferlib/capture/buck.py diff --git a/infer/lib/python/inferlib/capture/buck.py b/infer/lib/python/inferlib/capture/buck.py deleted file mode 100644 index 84375c716..000000000 --- a/infer/lib/python/inferlib/capture/buck.py +++ /dev/null @@ -1,253 +0,0 @@ -# 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. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import argparse -import logging -import os -import shutil -import subprocess -import tempfile -import traceback -import time - -from inferlib import config, issues, utils -from . import util - -import re - -MODULE_NAME = __name__ -MODULE_DESCRIPTION = '''Run analysis of code built with a command like: -buck [options] [target] - -Analysis examples: -infer --buck-clang -- buck build HelloWorld''' -LANG = ['clang'] - -KEEP_GOING_OPTION = "--keep-going" - - -def gen_instance(*args): - return BuckAnalyzer(*args) - - -def string_in_quotes(value): - return value.strip('\'') - - -def create_argparser(group_name=MODULE_NAME): - """This defines the set of arguments that get added by this module to the - set of global args defined in the infer top-level module - Do not use this function directly, it should be invoked by the infer - top-level module""" - parser = argparse.ArgumentParser(add_help=False) - group = parser.add_argument_group( - '{grp} module'.format(grp=MODULE_NAME), - description=MODULE_DESCRIPTION, - ) - group.add_argument('--xcode-developer-dir', - help='Specify the path to Xcode developer directory ') - group.add_argument('--blacklist-regex', - help='Specify the regex for files to skip during ' - 'the analysis') - group.add_argument('--Xbuck', action='append', default=[], - type=string_in_quotes, - help='Pass values as command-line arguments to ' - 'invocations of `buck build`.' - 'NOTE: value should be wrapped in single quotes') - group.add_argument('--buck-merge-all-deps', - action='store_true', - help='Find and merge all deps produced by buck') - - return parser - - -class BuckAnalyzer: - def __init__(self, args, cmd): - self.args = args - self.cmd = cmd - self.keep_going = KEEP_GOING_OPTION in self.args.Xbuck - util.log_java_version() - logging.info(util.run_cmd_ignore_fail(['buck', '--version'])) - - def capture(self): - try: - return self.capture_with_flavors() - except subprocess.CalledProcessError as exc: - if self.args.debug: - traceback.print_exc() - return exc.returncode - - def create_cxx_buck_configuration_args(self): - # return a string that can be passed in input to buck - # and configures the paths to infer/clang/plugin/xcode - facebook_clang_plugins_root = config.FCP_DIRECTORY - clang_path = os.path.join( - facebook_clang_plugins_root, - 'clang', - 'install', - 'bin', - 'clang', - ) - plugin_path = os.path.join( - facebook_clang_plugins_root, - 'libtooling', - 'build', - 'FacebookClangPlugin.dylib', - ) - args = [ - '--config', - 'client.id=infer.clang', - '--config', - '*//infer.infer_bin={bin}' - .format(bin=config.BIN_DIRECTORY), - '--config', - '*//infer.clang_compiler={clang}'.format(clang=clang_path), - '--config', - '*//infer.clang_plugin={plugin}'.format(plugin=plugin_path), - '--config', - '*//cxx.pch_enabled=false', - '--config', # Infer doesn't support C++ modules yet (T35656509) - '*//cxx.modules_default=false', - '--config', - '*//cxx.modules=False', - ] + self.args.Xbuck - - if self.args.xcode_developer_dir is not None: - args.append('--config') - args.append('apple.xcode_developer_dir={devdir}'.format( - devdir=self.args.xcode_developer_dir)) - if self.args.blacklist_regex: - args.append('--config') - args.append('*//infer.blacklist_regex={regex}'.format( - regex=self.args.blacklist_regex)) - return args - - def _get_analysis_result_paths(self): - # TODO(8610738): Make targets extraction smarter - buck_results_cmd = [ - self.cmd[0], - 'targets', - '--show-output' - ] + self.cmd[2:] + self.create_cxx_buck_configuration_args() - buck_results_cmd = \ - [x for x in buck_results_cmd if x != KEEP_GOING_OPTION] - proc = subprocess.Popen(buck_results_cmd, stdout=subprocess.PIPE) - (buck_output, _) = proc.communicate() - if proc.returncode != 0: - return None - # remove target name prefixes from each line and split them into a list - out = [os.path.join(self.args.project_root, x.split(None, 1)[1]) - for x in buck_output.strip().split('\n')] - return [os.path.dirname(x) - if os.path.isfile(x) else x - for x in out if os.path.exists(x)] - - @staticmethod - def _merge_infer_dep_files(root_paths, merged_out_path): - potential_dep_files = [os.path.join(p, config.INFER_BUCK_DEPS_FILENAME) - for p in root_paths] - dep_files = filter(os.path.exists, potential_dep_files) - utils.merge_and_dedup_files_into_path(dep_files, merged_out_path) - - @staticmethod - def _find_deps_and_merge(merged_out_path): - """This function is used to compute the infer-deps.txt file that - contains the location of the infer-out folders with the captured - files created by buck. This is needed when keep-going is passed - to buck and there are compilation failures, because in that case - buck doesn't create this file.""" - infer_out_folders = [] - start_time = time.time() - print('finding captured files in buck-out...') - for root, dirs, files in os.walk(config.BUCK_OUT_GEN): - regex = re.compile('.*infer-out.*') - folders = \ - [os.path.join(root, d) for d in dirs if re.match(regex, d)] - for d in folders: - if d not in infer_out_folders: - infer_out_folders.append(d) - with open(merged_out_path, 'w') as fmerged_out_path: - for dir in infer_out_folders: - fmerged_out_path.write('\t' + '\t' + dir + '\n') - elapsed_time = time.time() - start_time - print('time elapsed in finding captured files in buck-out: % 6.2fs' - % elapsed_time) - - def _find_depsfiles_and_merge(self, merge_out_path): - """ Sometimes buck targets --show-output gets confused and returns a - folder that doesn't contain infer-deps.txt. This can happen with on - for example objc targes with a certain combination of BUCK modes and - flavours. This function will walk buck-out and find infer-deps.txt - It will merge ALL infer-deps.txt in buck-out, so you might want - to do a buck clean first.""" - fs = [] - for root, dirs, files in os.walk(config.BUCK_OUT_GEN): - fs += [os.path.dirname(os.path.join(root, f)) for f in files - if f == config.INFER_BUCK_DEPS_FILENAME] - self._merge_infer_dep_files(fs, merge_out_path) - - def _move_buck_out(self): - """ If keep-going is passed, we may need to compute the infer-deps - file with the paths to the captured files. To make sure that - this is done in a consistent way, we need to start the analysis - with an empty buck-out folder.""" - if not os.path.exists(config.BUCK_OUT_TRASH): - os.makedirs(config.BUCK_OUT_TRASH) - tmp = tempfile.mkdtemp( - dir=config.BUCK_OUT_TRASH, - prefix=config.BUCK_OUT) - print('moving files in ' + config.BUCK_OUT + ' to ' + tmp) - for filename in os.listdir(config.BUCK_OUT): - if filename != config.TRASH: - shutil.move(os.path.join(config.BUCK_OUT, filename), tmp) - - def _run_buck_with_flavors(self): - env_vars = utils.read_env() - infer_args = env_vars['INFER_ARGS'] - if infer_args != '': - infer_args += '^' # '^' must be CommandLineOption.env_var_sep - infer_args += '--fcp-syntax-only' - env_vars['INFER_ARGS'] = infer_args - env = utils.encode_env(env_vars) - command = self.cmd - command += ['-j', str(self.args.multicore)] - if self.args.load_average is not None: - command += ['-L', str(self.args.load_average)] - command += self.create_cxx_buck_configuration_args() - try: - subprocess.check_call(command, env=env) - return os.EX_OK - except subprocess.CalledProcessError as e: - if self.keep_going: - print('Buck failed, but continuing the analysis ' - 'because --keep-going was passed') - return -1 - else: - raise e - - def capture_with_flavors(self): - if self.keep_going and not self.args.continue_capture: - self._move_buck_out() - ret = self._run_buck_with_flavors() - if not ret == os.EX_OK and not self.keep_going: - return ret - result_paths = self._get_analysis_result_paths() - if result_paths is None: - # huho, the Buck command to extract results paths failed - return os.EX_SOFTWARE - merged_deps_path = os.path.join( - self.args.infer_out, config.INFER_BUCK_DEPS_FILENAME) - if (not ret == os.EX_OK and self.keep_going): - self._find_deps_and_merge(merged_deps_path) - elif self.args.buck_merge_all_deps: - self._find_depsfiles_and_merge(merged_deps_path) - else: - self._merge_infer_dep_files(result_paths, merged_deps_path) - return os.EX_OK diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index fd3be1a01..41143d03b 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -155,6 +155,8 @@ let bound_error_allowed_in_procedure_call = true let buck_infer_deps_file_name = "infer-deps.txt" +let buck_out_gen = "buck-out" ^/ "gen" + let buck_results_dir_name = "infer" let captured_dir_name = "captured" @@ -414,6 +416,12 @@ let bin_dir = Filename.dirname (Utils.realpath Sys.executable_name) +let fcp_dir = + bin_dir ^/ Filename.parent_dir_name ^/ Filename.parent_dir_name ^/ "facebook-clang-plugins" + + +let clang_plugin_path = fcp_dir ^/ "libtooling" ^/ "build" ^/ "FacebookClangPlugin.dylib" + let lib_dir = bin_dir ^/ Filename.parent_dir_name ^/ "lib" let etc_dir = bin_dir ^/ Filename.parent_dir_name ^/ "etc" diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 32a98be7c..9164388e4 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -27,8 +27,6 @@ type build_system = type scheduler = File | Restart | SyntacticCallGraph [@@deriving equal] -val equal_build_system : build_system -> build_system -> bool - val build_system_of_exe_name : string -> build_system val string_of_build_system : build_system -> string @@ -65,6 +63,8 @@ val clang_initializer_prefix : string val clang_inner_destructor_prefix : string +val clang_plugin_path : string + val classnames_dir_name : string val classpath : string option @@ -87,6 +87,8 @@ val events_dir_name : string val fail_on_issue_exit_code : int +val fcp_dir : string + val frontend_stats_dir_name : string val global_tenv_filename : string @@ -250,6 +252,8 @@ val buck_mode : BuckMode.t option val buck_out : string option +val buck_out_gen : string + val buck_targets_blacklist : string list val bufferoverrun : bool diff --git a/infer/src/base/Utils.ml b/infer/src/base/Utils.ml index 094662e88..b5eed6a7a 100644 --- a/infer/src/base/Utils.ml +++ b/infer/src/base/Utils.ml @@ -26,12 +26,31 @@ let find_files ~path ~extension = traverse_dir_aux files full_path | _ -> files + | exception Unix.Unix_error (ENOENT, _, _) -> + files in Sys.fold_dir ~init ~f:(aux dir_path) dir_path in traverse_dir_aux [] path +let fold_folders ~init ~f ~path = + let rec traverse_dir_aux acc dir_path = + let aux base_path acc' rel_path = + let full_path = base_path ^/ rel_path in + match (Unix.stat full_path).Unix.st_kind with + | Unix.S_DIR -> + traverse_dir_aux (f acc' full_path) full_path + | _ -> + acc' + | exception Unix.Unix_error (ENOENT, _, _) -> + acc' + in + Sys.fold_dir ~init:acc ~f:(aux dir_path) dir_path + in + traverse_dir_aux init path + + (** read a source file and return a list of lines, or None in case of error *) let read_file fname = let res = ref [] in diff --git a/infer/src/base/Utils.mli b/infer/src/base/Utils.mli index 1238f3a5d..932b12df5 100644 --- a/infer/src/base/Utils.mli +++ b/infer/src/base/Utils.mli @@ -14,6 +14,9 @@ val initial_times : Unix.process_times val find_files : path:string -> extension:string -> string list (** recursively traverse a path for files ending with a given extension *) +val fold_folders : init:'acc -> f:('acc -> string -> 'acc) -> path:string -> 'acc +(** recursively traverse a path for folders, returning resuls by a given fold function *) + val string_crc_hex32 : string -> string (** Compute a 32-character hexadecimal crc using the Digest module *) diff --git a/infer/src/clang/ClangCommand.ml b/infer/src/clang/ClangCommand.ml index 58f5f7963..06f2901d4 100644 --- a/infer/src/clang/ClangCommand.ml +++ b/infer/src/clang/ClangCommand.ml @@ -15,13 +15,6 @@ type t = ; quoting_style: ClangQuotes.style ; is_driver: bool } -let fcp_dir = - Config.bin_dir ^/ Filename.parent_dir_name ^/ Filename.parent_dir_name ^/ "facebook-clang-plugins" - - -(** path of the plugin to load in clang *) -let plugin_path = fcp_dir ^/ "libtooling" ^/ "build" ^/ "FacebookClangPlugin.dylib" - (** name of the plugin to use *) let plugin_name = "BiniouASTExporter" @@ -148,7 +141,7 @@ let filter_and_replace_unsupported_args ?(replace_options_arg = fun _ s -> s) ?( let clang_cc1_cmd_sanitizer cmd = let replace_args arg = function | Some override_regex when Str.string_match override_regex arg 0 -> - fcp_dir ^/ "clang" ^/ "install" ^/ "lib" ^/ "clang" ^/ "9.0.0" ^/ "include" + Config.fcp_dir ^/ "clang" ^/ "install" ^/ "lib" ^/ "clang" ^/ "9.0.0" ^/ "include" | _ -> arg in @@ -175,7 +168,7 @@ let clang_cc1_cmd_sanitizer cmd = match libcxx_include_to_override_regex with | Some libcxx_include_to_override_regex when Str.string_match libcxx_include_to_override_regex arg 0 -> - fcp_dir ^/ "clang" ^/ "install" ^/ "include" ^/ "c++" ^/ "v1" + Config.fcp_dir ^/ "clang" ^/ "install" ^/ "include" ^/ "c++" ^/ "v1" | _ -> arg ) | _ -> @@ -250,7 +243,7 @@ let with_plugin_args args = argv_cons "-cc1" |> List.rev_append [ "-load" - ; plugin_path + ; Config.clang_plugin_path ; (* (t7400979) this is a workaround to avoid that clang crashes when the -fmodules flag and the YojsonASTExporter plugin are used. Since the -plugin argument disables the generation of .o files, we invoke apple clang again to generate the expected artifacts. This will keep diff --git a/infer/src/integration/Buck.ml b/infer/src/integration/Buck.ml index 246196cd6..672c85909 100644 --- a/infer/src/integration/Buck.ml +++ b/infer/src/integration/Buck.ml @@ -339,3 +339,109 @@ 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" ] + ( 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 [] ) + @ ( 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 -> [] ) + + +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 {Unix.Process_info.stdin; stdout; stderr; pid} = + Unix.create_process ~prog ~args:buck_build_args + in + let buck_stderr = Unix.in_channel_of_descr stderr in + let buck_stdout = Unix.in_channel_of_descr stdout in + Utils.with_channel_in buck_stderr ~f:(L.progress "BUCK: %s@.") ; + Unix.close stdin ; + In_channel.close buck_stderr ; + (* 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 = Config.project_root ^/ target_path ^/ Config.buck_infer_deps_file_name in + if PolyVariantEqual.(Sys.file_exists filename = `Yes) then filename :: acc else acc + | _ -> + L.die ExternalError "Couldn't parse buck target output: %s" line + in + match Unix.waitpid pid with + | Ok () -> + let res = In_channel.fold_lines buck_stdout ~init:[] ~f:process_buck_line in + In_channel.close buck_stdout ; res + | Error _ as err -> + L.die ExternalError "*** capture failed to execute: %s" + (Unix.Exit_or_signal.to_string_hum err) + + +let merge_deps_files depsfiles = + let buck_out = Config.project_root ^/ Config.buck_out_gen in + let depslines, depsfiles = + match (depsfiles, Config.keep_going, Config.buck_merge_all_deps) with + | [], true, _ -> + 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 @@ dir ^/ ResultsDatabase.database_filename = `Yes) + then Printf.sprintf "\t\t%s" dir :: acc + else acc ) + in + (infouts, depsfiles) + | [], _, true -> + let files = Utils.find_files ~path:buck_out ~extension:Config.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 = Config.(results_dir ^/ buck_infer_deps_file_name) 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 eb8724f6b..f734e6fc8 100644 --- a/infer/src/integration/Buck.mli +++ b/infer/src/integration/Buck.mli @@ -32,3 +32,6 @@ val store_args_in_file : string list -> string list 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/Driver.ml b/infer/src/integration/Driver.ml index 3b3fe2d3a..296e16173 100644 --- a/infer/src/integration/Driver.ml +++ b/infer/src/integration/Driver.ml @@ -16,6 +16,7 @@ module F = Format (* based on the build_system and options passed to infer, we run in different driver modes *) type mode = | Analyze + | BuckClangFlavor of string list | BuckGenrule of string | BuckGenruleMaster of string list | BuckCompilationDB of BuckMode.clang_compilation_db_deps * string * string list @@ -31,6 +32,8 @@ let is_analyze_mode = function Analyze -> true | _ -> false let pp_mode fmt = function | Analyze -> F.fprintf fmt "Analyze driver mode" + | BuckClangFlavor args -> + F.fprintf fmt "BuckClangFlavor driver mode: args = %a" Pp.cli_args args | BuckGenrule prog -> F.fprintf fmt "BuckGenRule driver mode:@\nprog = '%s'" prog | BuckGenruleMaster build_cmd -> @@ -187,12 +190,12 @@ let capture_with_compilation_database db_files = CaptureCompilationDatabase.capture_files_in_database compilation_database -let python_capture build_system build_cmd = +let buck_capture build_cmd = register_perf_stats_report PerfStats.TotalFrontend ; - let in_buck_mode = Config.equal_build_system build_system BBuck in - let build_cmd_opt = + 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 when in_buck_mode -> + | Some ClangFlavors -> (* let children infer processes know that they are inside Buck *) let infer_args_with_buck = String.concat @@ -200,7 +203,6 @@ let python_capture build_system build_cmd = (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--buck"]) in Unix.putenv ~key:CLOpt.args_env_var ~data:infer_args_with_buck ; - let prog, buck_args = (List.hd_exn build_cmd, List.tl_exn build_cmd) in let {Buck.command; rev_not_targets; targets} = Buck.add_flavors_to_buck_arguments ClangFlavors ~filter_kind:`Auto ~extra_flavors:[] buck_args @@ -209,71 +211,66 @@ let python_capture build_system build_cmd = else let all_args = List.rev_append rev_not_targets targets in let updated_buck_cmd = - [prog; command] - @ List.rev_append Config.buck_build_args_no_inline (Buck.store_args_in_file all_args) + 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 updated_buck_cmd + Some (prog, updated_buck_cmd) | _ -> - Some build_cmd + Some (prog, build_cmd) in - Option.iter build_cmd_opt ~f:(fun updated_build_cmd -> - L.progress "Capturing in %s mode...@." (Config.string_of_build_system build_system) ; - let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in - let args = - List.rev_append Config.anon_args - ( ( match (build_system, Config.buck_blacklist) with - | Config.BBuck, _ :: _ -> - ["--blacklist-regex"; "(" ^ String.concat ~sep:")|(" Config.buck_blacklist ^ ")"] - | _ -> - [] ) - @ (if not Config.continue_capture then [] else ["--continue"]) - @ ( match Config.force_integration with - | None -> - [] - | Some tool -> - ["--force-integration"; Config.string_of_build_system tool] ) - @ (match Config.java_jar_compiler with None -> [] | Some p -> ["--java-jar-compiler"; p]) - @ ( match List.rev Config.buck_build_args with - | args when in_buck_mode -> - List.map ~f:(fun arg -> ["--Xbuck"; "'" ^ arg ^ "'"]) args |> List.concat - | _ -> - [] ) - @ (if not Config.debug_mode then [] else ["--debug"]) - @ (if Config.filtering then [] else ["--no-filtering"]) - @ "-j" :: string_of_int Config.jobs - :: (match Config.load_average with None -> [] | Some l -> ["-l"; string_of_float l]) - @ (if not Config.pmd_xml then [] else ["--pmd-xml"]) - @ ["--project-root"; Config.project_root] - @ (if not Config.quiet then [] else ["--quiet"]) - @ "--out" :: Config.results_dir - :: - ( match Config.xcode_developer_dir with - | None -> - [] - | Some d -> - ["--xcode-developer-dir"; d] ) - @ (if not Config.buck_merge_all_deps then [] else ["--buck-merge-all-deps"]) - @ ("--" :: updated_build_cmd) ) - in - if in_buck_mode && Option.exists ~f:BuckMode.is_clang_flavors Config.buck_mode then ( + 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 ( RunState.set_merge_capture true ; RunState.store () ) ; - run_command ~prog:infer_py ~args - ~cleanup:(function - | Error (`Exit_non_zero exit_code) - when Int.equal exit_code Config.infer_py_argparse_error_exit_code -> - (* swallow infer.py argument parsing error *) - Config.print_usage_exit () - | status -> - command_error_handling ~always_die:true ~prog:infer_py ~args status ) - () ; - PerfStats.get_reporter PerfStats.TotalFrontend () ) + Buck.clang_flavor_capture ~prog ~buck_build_cmd ) ; + PerfStats.get_reporter PerfStats.TotalFrontend () + + +let python_capture build_system build_cmd = + register_perf_stats_report PerfStats.TotalFrontend ; + L.progress "Capturing in %s mode...@." (Config.string_of_build_system build_system) ; + let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in + let args = + List.rev_append Config.anon_args + ( (if not Config.continue_capture then [] else ["--continue"]) + @ ( match Config.force_integration with + | None -> + [] + | Some tool -> + ["--force-integration"; Config.string_of_build_system tool] ) + @ (match Config.java_jar_compiler with None -> [] | Some p -> ["--java-jar-compiler"; p]) + @ (if not Config.debug_mode then [] else ["--debug"]) + @ (if Config.filtering then [] else ["--no-filtering"]) + @ "-j" :: string_of_int Config.jobs + :: (match Config.load_average with None -> [] | Some l -> ["-l"; string_of_float l]) + @ (if not Config.pmd_xml then [] else ["--pmd-xml"]) + @ ["--project-root"; Config.project_root] + @ (if not Config.quiet then [] else ["--quiet"]) + @ "--out" :: Config.results_dir + :: + (match Config.xcode_developer_dir with None -> [] | Some d -> ["--xcode-developer-dir"; d]) + @ (if not Config.buck_merge_all_deps then [] else ["--buck-merge-all-deps"]) + @ ("--" :: build_cmd) ) + in + run_command ~prog:infer_py ~args + ~cleanup:(function + | Error (`Exit_non_zero exit_code) + when Int.equal exit_code Config.infer_py_argparse_error_exit_code -> + (* swallow infer.py argument parsing error *) + Config.print_usage_exit () + | status -> + command_error_handling ~always_die:true ~prog:infer_py ~args status ) + () ; + PerfStats.get_reporter PerfStats.TotalFrontend () let capture ~changed_files = function | Analyze -> () + | BuckClangFlavor build_cmd -> + buck_capture build_cmd | BuckCompilationDB (deps, prog, args) -> L.progress "Capturing using Buck's compilation database...@." ; let json_cdb = @@ -391,8 +388,7 @@ let error_nothing_to_analyze mode = let analyze_and_report ?suppress_console_report ~changed_files mode = let should_analyze, should_report = match (Config.command, mode) with - | _, PythonCapture (BBuck, _) - when not (Option.exists ~f:BuckMode.is_clang_flavors Config.buck_mode) -> + | _, BuckClangFlavor _ when not (Option.exists ~f:BuckMode.is_clang_flavors Config.buck_mode) -> (* In Buck mode when compilation db is not used, analysis is invoked from capture if buck flavors are not used *) (false, false) | _ when Config.infer_is_clang || Config.infer_is_javac -> @@ -409,7 +405,7 @@ let analyze_and_report ?suppress_console_report ~changed_files mode = | _ when Config.merge -> (* [--merge] overrides other behaviors *) true - | PythonCapture (BBuck, _) + | BuckClangFlavor _ when Option.exists ~f:BuckMode.is_clang_flavors Config.buck_mode && InferCommand.equal Run Config.command -> (* if doing capture + analysis of buck with flavors, we always need to merge targets before the analysis phase *) @@ -532,7 +528,7 @@ let mode_of_build_command build_cmd (buck_mode : BuckMode.t option) = L.user_warning "WARNING: the linters require --buck-compilation-database to be set.@ Alternatively, \ set --no-linters to disable them and this warning.@." ; - PythonCapture (BBuck, build_cmd) + BuckClangFlavor build_cmd | BBuck, Some JavaGenruleMaster -> BuckGenruleMaster build_cmd | BClang, _ -> @@ -547,7 +543,8 @@ let mode_of_build_command build_cmd (buck_mode : BuckMode.t option) = Maven (prog, args) | BXcode, _ when Config.xcpretty -> XcodeXcpretty (prog, args) - | (BBuck as build_system), Some ClangFlavors + | BBuck, Some ClangFlavors -> + BuckClangFlavor build_cmd | ((BAnt | BGradle | BNdk | BXcode) as build_system), _ -> PythonCapture (build_system, build_cmd) ) diff --git a/infer/src/integration/Driver.mli b/infer/src/integration/Driver.mli index aa05248ad..5527f158f 100644 --- a/infer/src/integration/Driver.mli +++ b/infer/src/integration/Driver.mli @@ -13,6 +13,7 @@ open! IStd (** based on the build_system and options passed to infer, we run in different driver modes *) type mode = | Analyze + | BuckClangFlavor of string list | BuckGenrule of string | BuckGenruleMaster of string list | BuckCompilationDB of BuckMode.clang_compilation_db_deps * string * string list