[driver] move things around

Summary:
This will be needed to re-use the functions now in Driver.ml in other contexts
without always adding to infer.ml. For instance, this is used in a later diff
to do a diff-analysis orchestrator that needs to run the capture and analysis
several times.

Reviewed By: jberdine

Differential Revision: D5319862

fbshipit-source-id: caf9551
master
Jules Villard 8 years ago committed by Facebook Github Bot
parent 83148a71aa
commit 2f569e2b97

@ -272,7 +272,7 @@ rei:
%.rei : %.mli
$(SCRIPT_DIR)/refmt.sh -parse ml -print re $< > $*.rei
roots:=Infer StatsAggregator
roots:=Infer
ifeq ($(IS_FACEBOOK_TREE),yes)
roots += $(INFER_CREATE_TRACEVIEW_LINKS_MODULE)
endif

@ -18,500 +18,6 @@ module L = Logging
module F = Format
let rec rmtree name =
match Unix.((lstat name).st_kind) with
| S_DIR ->
let dir = Unix.opendir name in
let rec rmdir dir =
match Unix.readdir dir with
| entry ->
if not (String.equal entry Filename.current_dir_name ||
String.equal entry Filename.parent_dir_name)
then (
rmtree (name ^/ entry)
);
rmdir dir
| exception End_of_file ->
Unix.closedir dir ;
Unix.rmdir name in
rmdir dir
| _ ->
Unix.unlink name
| exception Unix.Unix_error (Unix.ENOENT, _, _) ->
()
type build_system =
| BAnalyze | BAnt | BBuck | BClang | BGradle | BJava | BJavac | BMake | BMvn
| BNdk | BXcode
[@@deriving compare]
let equal_build_system = [%compare.equal : build_system]
(* List of ([build system], [executable name]). Several executables may map to the same build
system. In that case, the first one in the list will be used for printing, eg, in which mode
infer is running. *)
let build_system_exe_assoc = [
BAnalyze, "analyze"; BAnt, "ant"; BBuck, "buck"; BGradle, "gradle"; BGradle, "gradlew";
BJava, "java"; BJavac, "javac";
BClang, "cc"; BClang, "clang"; BClang, "gcc"; BClang, "clang++"; BClang, "c++"; BClang, "g++";
BMake, "make"; BMake, "configure"; BMake, "cmake"; BMake, "waf";
BMvn, "mvn"; BMvn, "mvnw"; BNdk, "ndk-build"; BXcode, "xcodebuild";
]
let build_system_of_exe_name name =
try
List.Assoc.find_exn (List.Assoc.inverse build_system_exe_assoc) name
with Not_found ->
invalid_argf "Unsupported build command %s" name
let string_of_build_system build_system =
List.Assoc.find_exn build_system_exe_assoc build_system
(* based on the build_system and options passed to infer, we run in different driver modes *)
type driver_mode =
| Analyze
| BuckGenrule of string
| BuckCompilationDB of string * string list
| Clang of Clang.compiler * string * string list
| ClangCompilationDB of [ `Escaped of string | `Raw of string ] list
| Javac of Javac.compiler * string * string list
| Maven of string * string list
| PythonCapture of build_system * string list
| XcodeXcpretty of string * string list
[@@deriving compare]
let equal_driver_mode = [%compare.equal : driver_mode]
let pp_driver_mode fmt driver_mode =
let log_argfile_arg fname =
try
F.fprintf fmt "-- Contents of '%s'@\n" fname;
In_channel.iter_lines ~f:(F.fprintf fmt "%s@\n") (In_channel.create fname);
F.fprintf fmt "-- /Contents of '%s'@." fname;
with exn ->
F.fprintf fmt " Error reading file '%s':@\n %a@." fname Exn.pp exn in
match driver_mode with
| Analyze | BuckGenrule _ | BuckCompilationDB _ | ClangCompilationDB _ | PythonCapture (_,_)
| XcodeXcpretty _ ->
(* these are pretty boring, do not log anything *)
()
| Javac (_, prog, args) ->
F.fprintf fmt "Javac driver mode:@\nprog = %s@\n" prog;
let log_arg arg =
F.fprintf fmt "Arg: %s@\n" arg;
(* "@fname" means that fname is an arg file containing additional arguments to pass to
javac. *)
String.chop_prefix ~prefix:"@" arg
|>
(* Sometimes these argfiles go away at the end of the build and we cannot inspect them after
the fact, so log them now. *)
Option.iter ~f:log_argfile_arg in
List.iter ~f:log_arg args
| Maven (prog, args) ->
F.fprintf fmt "Maven driver mode:@\nprog = %s@\n" prog;
List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args
| Clang (_, prog, args) ->
F.fprintf fmt "Clang driver mode:@\nprog = %s@\n" prog;
List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args
(* A clean command for each driver mode to be suggested to the user
in case nothing got captured. *)
let clean_compilation_command driver_mode =
match driver_mode with
| BuckCompilationDB (prog, _)
| Clang (_, prog, _) ->
Some (prog ^ " clean")
| XcodeXcpretty (prog, args) ->
Some (String.concat ~sep:" " (List.append (prog::args) ["clean"]))
| _ -> None
let remove_results_dir () =
rmtree Config.results_dir
let create_results_dir () =
Unix.mkdir_p (Config.results_dir ^/ Config.attributes_dir_name) ;
Unix.mkdir_p (Config.results_dir ^/ Config.captured_dir_name) ;
Unix.mkdir_p (Config.results_dir ^/ Config.specs_dir_name);
L.setup_log_file ()
(* Clean up the results dir to select only what's relevant to go in the Buck cache. In particular,
get rid of non-deterministic outputs.*)
let clean_results_dir () =
(* In Buck flavors mode we keep all capture data, but in Java mode we keep only the tenv *)
let should_delete_dir =
let dirs_to_delete =
"backend_stats" :: "classnames" :: "filelists" :: "frontend_stats" :: "multicore" ::
"reporting_stats" :: "sources" ::
if Config.flavors then []
else ["attributes"] in
List.mem ~equal:String.equal dirs_to_delete in
let should_delete_file =
let suffixes_to_delete =
".txt" :: ".csv" :: ".json" ::
if Config.flavors then []
else [ ".cfg"; ".cg" ] in
fun name ->
(* Keep the JSON report *)
not (String.equal (Filename.basename name) "report.json")
&& List.exists ~f:(Filename.check_suffix name) suffixes_to_delete in
let rec clean name =
let rec cleandir dir = match Unix.readdir dir with
| entry ->
if should_delete_dir entry then (
rmtree (name ^/ entry)
) else if not (String.equal entry Filename.current_dir_name
|| String.equal entry Filename.parent_dir_name) then (
clean (name ^/ entry)
);
cleandir dir (* next entry *)
| exception End_of_file ->
Unix.closedir dir in
match Unix.opendir name with
| dir ->
cleandir dir
| exception Unix.Unix_error (Unix.ENOTDIR, _, _) ->
if should_delete_file name then
Unix.unlink name;
()
| exception Unix.Unix_error (Unix.ENOENT, _, _) ->
() in
clean Config.results_dir
let check_captured_empty driver_mode =
let clean_command_opt = clean_compilation_command driver_mode in
(* if merge is passed, the captured folder will be empty at this point,
but will be filled later on. *)
if Utils.dir_is_empty Config.captured_dir && not Config.merge then ((
match clean_command_opt with
| Some clean_command ->
L.user_warning "@\nNothing to compile. Try running `%s` first.@." clean_command
| None ->
L.user_warning "@\nNothing to compile. Try cleaning the build first.@."
);
true
) else
false
let register_perf_stats_report () =
let stats_dir = Filename.concat Config.results_dir Config.backend_stats_dir_name in
let stats_base = Config.perf_stats_prefix ^ ".json" in
let stats_file = Filename.concat stats_dir stats_base in
PerfStats.register_report_at_exit stats_file
let reset_duplicates_file () =
let start = Config.results_dir ^/ Config.duplicates_filename in
let delete () =
Unix.unlink start in
let create () =
Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in
if Sys.file_exists start = `Yes then delete ();
create ()
(* Create the .start file, and update the timestamp unless in continue mode *)
let touch_start_file_unless_continue () =
let start = Config.results_dir ^/ Config.start_filename in
let delete () =
Unix.unlink start in
let create () =
Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in
if not (Sys.file_exists start = `Yes) then create ()
else if not Config.continue_capture then (delete (); create ())
let run_command ~prog ~args cleanup =
Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())
|> fun status
-> cleanup status
; ok_exn (Unix.Exit_or_signal.or_error status)
let check_xcpretty () =
match Unix.system "xcpretty --version" with
| Ok () -> ()
| Error _ ->
L.user_error
"@\nxcpretty not found in the path. Please consider installing xcpretty \
for a more robust integration with xcodebuild. Otherwise use the option \
--no-xcpretty.@\n@."
let capture_with_compilation_database db_files =
let root = Unix.getcwd () in
Config.clang_compilation_dbs := List.map db_files ~f:(function
| `Escaped fname -> `Escaped (Utils.filename_to_absolute ~root fname)
| `Raw fname -> `Raw (Utils.filename_to_absolute ~root fname)
);
let compilation_database = CompilationDatabase.from_json_files db_files in
CaptureCompilationDatabase.capture_files_in_database compilation_database
let capture ~changed_files = function
| Analyze ->
()
| BuckCompilationDB (prog, args) ->
L.progress "Capturing using Buck's compilation database...@.";
let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck ~prog ~args in
capture_with_compilation_database ~changed_files json_cdb
| BuckGenrule path ->
L.progress "Capturing for Buck genrule compatibility...@.";
JMain.from_arguments path
| Clang (compiler, prog, args) ->
L.progress "Capturing in make/cc mode...@.";
Clang.capture compiler ~prog ~args
| ClangCompilationDB db_files ->
L.progress "Capturing using compilation database...@.";
capture_with_compilation_database ~changed_files db_files
| Javac (compiler, prog, args) ->
L.progress "Capturing in javac mode...@.";
Javac.capture compiler ~prog ~args
| Maven (prog, args) ->
L.progress "Capturing in maven mode...@.";
Maven.capture ~prog ~args
| PythonCapture (build_system, build_cmd) ->
L.progress "Capturing in %s mode...@." (string_of_build_system build_system);
let in_buck_mode = equal_build_system build_system BBuck in
let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in
let args =
List.rev_append Config.anon_args (
["--analyzer";
List.Assoc.find_exn ~equal:Config.equal_analyzer
(List.map ~f:(fun (n,a) -> (a,n)) Config.string_to_analyzer) Config.analyzer] @
(match Config.blacklist with
| Some s when in_buck_mode -> ["--blacklist-regex"; s]
| _ -> []) @
(if not Config.create_harness then [] else
["--android-harness"]) @
(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 not Config.debug_exceptions then [] else
["--debug-exceptions"]) @
(if Config.filtering then [] else
["--no-filtering"]) @
(if not Config.flavors || not in_buck_mode then [] else
["--use-flavors"]) @
"-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.reactive_mode then [] else
["--reactive"]) @
"--out" :: Config.results_dir ::
(match Config.xcode_developer_dir with None -> [] | Some d ->
["--xcode-developer-dir"; d]) @
"--" ::
if in_buck_mode && Config.flavors then
(* 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;
Buck.add_flavors_to_buck_command build_cmd
else build_cmd
) in
run_command ~prog:infer_py ~args
(function
| Result.Error (`Exit_non_zero exit_code) ->
if Int.equal exit_code Config.infer_py_argparse_error_exit_code then
(* swallow infer.py argument parsing error *)
Config.print_usage_exit ()
| _ ->
()
)
| XcodeXcpretty (prog, args) ->
L.progress "Capturing using xcodebuild and xcpretty...@.";
check_xcpretty ();
let json_cdb =
CaptureCompilationDatabase.get_compilation_database_files_xcodebuild ~prog ~args in
capture_with_compilation_database ~changed_files json_cdb
let run_parallel_analysis ~changed_files =
let multicore_dir = Config.results_dir ^/ Config.multicore_dir_name in
rmtree multicore_dir ;
Unix.mkdir_p multicore_dir ;
InferAnalyze.main ~changed_files ~makefile:(multicore_dir ^/ "Makefile") ;
run_command
~prog:"make" ~args:(
"-C" :: multicore_dir ::
"-k" ::
"-j" :: (string_of_int Config.jobs) ::
(Option.value_map ~f:(fun l -> ["-l"; string_of_float l]) ~default:[] Config.load_average) @
(if Config.debug_mode then [] else ["-s"])
) (fun _ -> ())
let execute_analyze ~changed_files =
if Int.equal Config.jobs 1 || Config.cluster_cmdline <> None then
InferAnalyze.main ~changed_files ~makefile:""
else
run_parallel_analysis ~changed_files
let report () =
let report_csv =
if Config.buck_cache_mode then None else Some (Config.results_dir ^/ "report.csv") in
let report_json = Some (Config.results_dir ^/ "report.json") in
InferPrint.main ~report_csv ~report_json ;
(* Post-process the report according to the user config. By default, calls report.py to create a
human-readable report.
Do not bother calling the report hook when called from within Buck or in quiet mode. *)
match Config.quiet || Config.buck_cache_mode, Config.report_hook with
| true, _
| false, None ->
()
| false, Some prog ->
let if_some key opt args = match opt with None -> args | Some arg -> key :: arg :: args in
let if_true key opt args = if not opt then args else key :: args in
let args =
if_some "--issues-csv" report_csv @@
if_some "--issues-json" report_json @@
if_some "--issues-txt" Config.bugs_txt @@
if_true "--pmd-xml" Config.pmd_xml [
"--project-root"; Config.project_root;
"--results-dir"; Config.results_dir
] in
if is_error (Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())) then
L.external_error
"** Error running the reporting script:@\n** %s %s@\n** See error above@."
prog (String.concat ~sep:" " args)
let analyze driver_mode ~changed_files =
let should_analyze, should_report = match driver_mode, Config.analyzer with
| PythonCapture (BBuck, _), _ ->
(* In Buck mode when compilation db is not used, analysis is invoked either from capture or
a separate Analyze invocation is necessary, depending on the buck flavor used. *)
false, false
| _ when Config.maven ->
(* Called from Maven, only do capture. *)
false, false
| _, (CaptureOnly | CompileOnly) ->
false, false
| _, (BiAbduction | Checkers | Crashcontext | Eradicate) ->
true, true
| _, Linters ->
false, true in
if (should_analyze || should_report) &&
(((Sys.file_exists Config.captured_dir) <> `Yes) ||
check_captured_empty driver_mode) then (
L.user_error "There was nothing to analyze.@\n@." ;
) else if should_analyze then
execute_analyze ~changed_files;
if should_report && Config.report then report ()
(** as the Config.fail_on_bug flag mandates, exit with error when an issue is reported *)
let fail_on_issue_epilogue () =
let issues_json = DB.Results_dir.(path_to_filename Abs_root ["report.json"])
|> DB.filename_to_string in
match Utils.read_file issues_json with
| Ok lines ->
let issues = Jsonbug_j.report_of_string @@ String.concat ~sep:"" lines in
if issues <> [] then exit Config.fail_on_issue_exit_code
| Error error ->
L.internal_error "Failed to read report file '%s': %s@." issues_json error ;
()
let log_infer_args driver_mode =
L.environment_info "INFER_ARGS = %s@\n"
(Option.value (Sys.getenv CLOpt.args_env_var) ~default:"<not found>");
List.iter ~f:(L.environment_info "anon arg: %s@\n") Config.anon_args;
List.iter ~f:(L.environment_info "rest arg: %s@\n") Config.rest;
L.environment_info "Project root = %s@\n" Config.project_root;
L.environment_info "CWD = %s@\n" (Sys.getcwd ());
L.environment_info "Driver mode:@\n%a@." pp_driver_mode driver_mode
let assert_supported_mode required_analyzer requested_mode_string =
let analyzer_enabled = match required_analyzer with
| `Clang -> Version.clang_enabled
| `Java -> Version.java_enabled
| `Xcode -> Version.clang_enabled && Version.xcode_enabled in
if not analyzer_enabled then
let analyzer_string = match required_analyzer with
| `Clang -> "clang"
| `Java -> "java"
| `Xcode -> "clang and xcode" in
failwithf
"Unsupported build mode: %s@\nInfer was built with %s analyzers disabled.@ Please rebuild \
infer with %s enabled.@."
requested_mode_string analyzer_string analyzer_string
let assert_supported_build_system build_system = match build_system with
| BAnt | BGradle | BJava | BJavac | BMvn ->
string_of_build_system build_system
|> assert_supported_mode `Java
| BClang | BMake | BNdk ->
string_of_build_system build_system
|> assert_supported_mode `Clang
| BXcode ->
string_of_build_system build_system
|> assert_supported_mode `Xcode
| BBuck ->
let (analyzer, build_string) =
if Config.flavors then
(`Clang, "buck with flavors")
else if Option.is_some Config.buck_compilation_database then
(`Clang, "buck compilation database")
else (
if Config.reactive_mode then
L.user_error
"WARNING: The reactive analysis mode is not compatible with the Buck integration for \
Java";
(`Java, string_of_build_system build_system)
) in
assert_supported_mode analyzer build_string
| BAnalyze ->
()
let driver_mode_of_build_cmd build_cmd =
match build_cmd with
| [] ->
if not (List.is_empty !Config.clang_compilation_dbs) then (
assert_supported_mode `Clang "clang compilation database";
ClangCompilationDB !Config.clang_compilation_dbs
) else
Analyze
| prog :: args ->
let build_system = build_system_of_exe_name (Filename.basename prog) in
assert_supported_build_system build_system;
match build_system_of_exe_name (Filename.basename prog) with
| BAnalyze ->
CLOpt.warnf
"WARNING: `infer -- analyze` is deprecated; \
use the `infer analyze` subcommand instead@.";
Analyze
| BBuck when Option.is_some Config.buck_compilation_database ->
BuckCompilationDB (prog, List.append args (List.rev Config.buck_build_args))
| BClang ->
Clang (Clang.Clang, prog, args)
| BMake ->
Clang (Clang.Make, prog, args)
| BJava ->
Javac (Javac.Java, prog, args)
| BJavac ->
Javac (Javac.Javac, prog, args)
| BMvn ->
Maven (prog, args)
| BXcode when Config.xcpretty ->
XcodeXcpretty (prog, args)
| BAnt | BBuck | BGradle | BNdk | BXcode as build_system ->
PythonCapture (build_system, build_cmd)
let get_driver_mode () =
match Config.generated_classes with
| _ when Config.maven ->
(* infer is pretending to be javac in the Maven integration *)
let build_args = match Array.to_list Sys.argv with
| _::args -> args
| [] -> [] in
Javac (Javac.Javac, "javac", build_args)
| Some path ->
assert_supported_mode `Java "Buck genrule";
BuckGenrule path
| None ->
driver_mode_of_build_cmd (List.rev Config.rest)
let read_config_changed_files () =
match Config.changed_files_index with
| None ->
@ -523,67 +29,22 @@ let read_config_changed_files () =
L.external_error "Error reading the changed files index '%s': %s@." index error ;
None
let infer_mode () =
let driver_mode = get_driver_mode () in
if not (equal_driver_mode driver_mode Analyze ||
Config.(buck || continue_capture || maven || reactive_mode)) then
remove_results_dir () ;
create_results_dir () ;
if CLOpt.is_originator then L.environment_info "%a@\n" Config.pp_version () ;
if Config.debug_mode || Config.stats_mode then log_infer_args driver_mode ;
if Config.dump_duplicate_symbols then reset_duplicates_file ();
(* infer might be called from a Makefile and itself uses `make` to run the analysis in parallel,
but cannot communicate with the parent make command. Since infer won't interfere with them
anyway, pretend that we are not called from another make to prevent make falling back to a
mono-threaded execution. *)
Unix.unsetenv "MAKEFLAGS";
register_perf_stats_report () ;
if not Config.buck_cache_mode then touch_start_file_unless_continue () ;
let run driver_mode =
let open Driver in
run_prologue driver_mode ;
let changed_files = read_config_changed_files () in
capture driver_mode ~changed_files ;
analyze driver_mode ~changed_files ;
if CLOpt.is_originator then (
let in_buck_mode = match driver_mode with | PythonCapture (BBuck, _) -> true | _ -> false in
StatsAggregator.generate_files () ;
if Config.equal_analyzer Config.analyzer Config.Crashcontext then
Crashcontext.crashcontext_epilogue ~in_buck_mode;
if Config.fail_on_bug then
fail_on_issue_epilogue () ;
);
if Config.buck_cache_mode then clean_results_dir ();
()
analyze_and_report driver_mode ~changed_files ;
run_epilogue driver_mode
let remove_results_dir () =
Utils.rmtree Config.results_dir
let differential_mode () =
(* at least one report must be passed in input to compute differential *)
(match Config.report_current, Config.report_previous with
| None, None ->
failwith "Expected at least one argument among 'report-current' and 'report-previous'@\n"
| _ -> ());
let load_report filename_opt : Jsonbug_t.report =
let empty_report = [] in
Option.value_map
~f:(fun filename -> Jsonbug_j.report_of_string (In_channel.read_all filename))
~default:empty_report filename_opt in
let current_report = load_report Config.report_current in
let previous_report = load_report Config.report_previous in
let diff =
let unfiltered_diff = Differential.of_reports ~current_report ~previous_report in
if Config.filtering then
let file_renamings = match Config.file_renamings with
| Some f -> DifferentialFilters.FileRenamings.from_json_file f
| None -> DifferentialFilters.FileRenamings.empty in
let interesting_paths = Option.map ~f:(fun fname ->
List.map ~f:(SourceFile.create ~warn_on_error:false) (In_channel.read_lines fname))
Config.differential_filter_files in
DifferentialFilters.do_filter
unfiltered_diff
file_renamings
~skip_duplicated_types:Config.skip_duplicated_types
~interesting_paths
else unfiltered_diff in
let out_path = Config.results_dir ^/ "differential" in
Unix.mkdir_p out_path;
Differential.to_files diff out_path
let create_results_dir () =
Unix.mkdir_p (Config.results_dir ^/ Config.attributes_dir_name) ;
Unix.mkdir_p (Config.results_dir ^/ Config.captured_dir_name) ;
Unix.mkdir_p (Config.results_dir ^/ Config.specs_dir_name);
L.setup_log_file ()
let assert_results_dir advice =
if Sys.file_exists Config.results_dir <> `Yes then (
@ -597,10 +58,12 @@ let setup_results_dir () =
match Config.command with
| Analyze -> assert_results_dir "have you run capture before?"
| Clang | Report | ReportDiff -> create_results_dir ()
| Capture | Compile | Run ->
(* These have their own logic depending on the rest of the command line. It particular they
might delete the previous results directory. Let them do their thing. *)
()
| Capture | Compile | Run ->
let driver_mode = Lazy.force Driver.mode_from_command_line in
if not (Driver.(equal_driver_mode driver_mode Analyze) ||
Config.(buck || continue_capture || maven || reactive_mode)) then
remove_results_dir ();
create_results_dir ()
let () =
if Config.print_builtins then Builtin.print_and_exit ();
@ -614,7 +77,7 @@ let () =
| Some cluster -> F.fprintf fmt "of cluster %s" (Filename.basename cluster) in
L.environment_info "Starting analysis %a" pp_cluster_opt Config.cluster_cmdline;
InferAnalyze.register_perf_stats_report ();
analyze Analyze ~changed_files:(read_config_changed_files ())
Driver.analyze_and_report Analyze ~changed_files:(read_config_changed_files ())
| Clang ->
let prog, args = match Array.to_list Sys.argv with
| prog::args -> prog, args
@ -623,6 +86,12 @@ let () =
| Report ->
InferPrint.main_from_config ()
| ReportDiff ->
differential_mode ()
(* at least one report must be passed in input to compute differential *)
(match Config.report_current, Config.report_previous with
| None, None ->
failwith "Expected at least one argument among 'report-current' and 'report-previous'\n"
| _ -> ());
ReportDiff.reportdiff ~current_report:Config.report_current
~previous_report:Config.report_previous
| Capture | Compile | Run ->
infer_mode ()
run (Lazy.force Driver.mode_from_command_line)

@ -315,3 +315,25 @@ let write_file_with_locking ?(delete=false) ~f:do_write fname =
if delete then
try Unix.unlink fname with
| Unix.Unix_error _ -> ()
let rec rmtree name =
match Unix.((lstat name).st_kind) with
| S_DIR ->
let dir = Unix.opendir name in
let rec rmdir dir =
match Unix.readdir dir with
| entry ->
if not (String.equal entry Filename.current_dir_name ||
String.equal entry Filename.parent_dir_name)
then (
rmtree (name ^/ entry)
);
rmdir dir
| exception End_of_file ->
Unix.closedir dir ;
Unix.rmdir name in
rmdir dir
| _ ->
Unix.unlink name
| exception Unix.Unix_error (Unix.ENOENT, _, _) ->
()

@ -89,3 +89,6 @@ val compare_versions : string -> string -> int
(** Lock file passed as argument and write into it using [f]. If [delete] then the file is unlinked
once this is done. *)
val write_file_with_locking : ?delete:bool -> f:(out_channel -> unit) -> string -> unit
(** [rmtree path] removes [path] and, if [path] is a directory, recursively removes its contents *)
val rmtree : string -> unit

@ -0,0 +1,518 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
open! PVariant
(** entry points for top-level functionalities such as capture, analysis, and reporting *)
module CLOpt = CommandLineOption
module L = Logging
module F = Format
type build_system =
| BAnalyze | BAnt | BBuck | BClang | BGradle | BJava | BJavac | BMake | BMvn
| BNdk | BXcode
[@@deriving compare]
let equal_build_system = [%compare.equal : build_system]
(* List of ([build system], [executable name]). Several executables may map to the same build
system. In that case, the first one in the list will be used for printing, eg, in which mode
infer is running. *)
let build_system_exe_assoc = [
BAnalyze, "analyze"; BAnt, "ant"; BBuck, "buck"; BGradle, "gradle"; BGradle, "gradlew";
BJava, "java"; BJavac, "javac";
BClang, "cc"; BClang, "clang"; BClang, "gcc"; BClang, "clang++"; BClang, "c++"; BClang, "g++";
BMake, "make"; BMake, "configure"; BMake, "cmake"; BMake, "waf";
BMvn, "mvn"; BMvn, "mvnw"; BNdk, "ndk-build"; BXcode, "xcodebuild";
]
let build_system_of_exe_name name =
try
List.Assoc.find_exn (List.Assoc.inverse build_system_exe_assoc) name
with Not_found ->
invalid_argf "Unsupported build command %s" name
let string_of_build_system build_system =
List.Assoc.find_exn build_system_exe_assoc build_system
(* based on the build_system and options passed to infer, we run in different driver modes *)
type mode =
| Analyze
| BuckGenrule of string
| BuckCompilationDB of string * string list
| Clang of Clang.compiler * string * string list
| ClangCompilationDB of [ `Escaped of string | `Raw of string ] list
| Javac of Javac.compiler * string * string list
| Maven of string * string list
| PythonCapture of build_system * string list
| XcodeXcpretty of string * string list
[@@deriving compare]
let equal_driver_mode = [%compare.equal : mode]
let pp_driver_mode fmt driver_mode =
let log_argfile_arg fname =
try
F.fprintf fmt "-- Contents of '%s'@\n" fname;
In_channel.iter_lines ~f:(F.fprintf fmt "%s@\n") (In_channel.create fname);
F.fprintf fmt "-- /Contents of '%s'@." fname;
with exn ->
F.fprintf fmt " Error reading file '%s':@\n %a@." fname Exn.pp exn in
match driver_mode with
| Analyze | BuckGenrule _ | BuckCompilationDB _ | ClangCompilationDB _ | PythonCapture (_,_)
| XcodeXcpretty _ ->
(* these are pretty boring, do not log anything *)
()
| Javac (_, prog, args) ->
F.fprintf fmt "Javac driver mode:@\nprog = %s@\n" prog;
let log_arg arg =
F.fprintf fmt "Arg: %s@\n" arg;
(* "@fname" means that fname is an arg file containing additional arguments to pass to
javac. *)
String.chop_prefix ~prefix:"@" arg
|>
(* Sometimes these argfiles go away at the end of the build and we cannot inspect them after
the fact, so log them now. *)
Option.iter ~f:log_argfile_arg in
List.iter ~f:log_arg args
| Maven (prog, args) ->
F.fprintf fmt "Maven driver mode:@\nprog = %s@\n" prog;
List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args
| Clang (_, prog, args) ->
F.fprintf fmt "Clang driver mode:@\nprog = %s@\n" prog;
List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args
(* A clean command for each driver mode to be suggested to the user
in case nothing got captured. *)
let clean_compilation_command driver_mode =
match driver_mode with
| BuckCompilationDB (prog, _)
| Clang (_, prog, _) ->
Some (prog ^ " clean")
| XcodeXcpretty (prog, args) ->
Some (String.concat ~sep:" " (List.append (prog::args) ["clean"]))
| _ -> None
(* Clean up the results dir to select only what's relevant to go in the Buck cache. In particular,
get rid of non-deterministic outputs.*)
let clean_results_dir () =
(* In Buck flavors mode we keep all capture data, but in Java mode we keep only the tenv *)
let should_delete_dir =
let dirs_to_delete =
"backend_stats" :: "classnames" :: "filelists" :: "frontend_stats" :: "multicore" ::
"reporting_stats" :: "sources" ::
if Config.flavors then []
else ["attributes"] in
List.mem ~equal:String.equal dirs_to_delete in
let should_delete_file =
let suffixes_to_delete =
".txt" :: ".csv" :: ".json" ::
if Config.flavors then []
else [ ".cfg"; ".cg" ] in
fun name ->
(* Keep the JSON report *)
not (String.equal (Filename.basename name) "report.json")
&& List.exists ~f:(Filename.check_suffix name) suffixes_to_delete in
let rec clean name =
let rec cleandir dir = match Unix.readdir dir with
| entry ->
if should_delete_dir entry then (
Utils.rmtree (name ^/ entry)
) else if not (String.equal entry Filename.current_dir_name
|| String.equal entry Filename.parent_dir_name) then (
clean (name ^/ entry)
);
cleandir dir (* next entry *)
| exception End_of_file ->
Unix.closedir dir in
match Unix.opendir name with
| dir ->
cleandir dir
| exception Unix.Unix_error (Unix.ENOTDIR, _, _) ->
if should_delete_file name then
Unix.unlink name;
()
| exception Unix.Unix_error (Unix.ENOENT, _, _) ->
() in
clean Config.results_dir
let check_captured_empty driver_mode =
let clean_command_opt = clean_compilation_command driver_mode in
(* if merge is passed, the captured folder will be empty at this point,
but will be filled later on. *)
if Utils.dir_is_empty Config.captured_dir && not Config.merge then ((
match clean_command_opt with
| Some clean_command ->
L.user_warning "@\nNothing to compile. Try running `%s` first.@." clean_command
| None ->
L.user_warning "@\nNothing to compile. Try cleaning the build first.@."
);
true
) else
false
let register_perf_stats_report () =
let stats_dir = Filename.concat Config.results_dir Config.backend_stats_dir_name in
let stats_base = Config.perf_stats_prefix ^ ".json" in
let stats_file = Filename.concat stats_dir stats_base in
PerfStats.register_report_at_exit stats_file
let reset_duplicates_file () =
let start = Config.results_dir ^/ Config.duplicates_filename in
let delete () =
Unix.unlink start in
let create () =
Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in
if Sys.file_exists start = `Yes then delete ();
create ()
(* Create the .start file, and update the timestamp unless in continue mode *)
let touch_start_file_unless_continue () =
let start = Config.results_dir ^/ Config.start_filename in
let delete () =
Unix.unlink start in
let create () =
Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in
if not (Sys.file_exists start = `Yes) then create ()
else if not Config.continue_capture then (delete (); create ())
let run_command ~prog ~args cleanup =
Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())
|> fun status
-> cleanup status
; ok_exn (Unix.Exit_or_signal.or_error status)
let check_xcpretty () =
match Unix.system "xcpretty --version" with
| Ok () -> ()
| Error _ ->
L.user_error
"@\nxcpretty not found in the path. Please consider installing xcpretty \
for a more robust integration with xcodebuild. Otherwise use the option \
--no-xcpretty.@\n@."
let capture_with_compilation_database db_files =
let root = Unix.getcwd () in
Config.clang_compilation_dbs := List.map db_files ~f:(function
| `Escaped fname -> `Escaped (Utils.filename_to_absolute ~root fname)
| `Raw fname -> `Raw (Utils.filename_to_absolute ~root fname)
);
let compilation_database = CompilationDatabase.from_json_files db_files in
CaptureCompilationDatabase.capture_files_in_database compilation_database
let capture ~changed_files = function
| Analyze ->
()
| BuckCompilationDB (prog, args) ->
L.progress "Capturing using Buck's compilation database...@.";
let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck ~prog ~args in
capture_with_compilation_database ~changed_files json_cdb
| BuckGenrule path ->
L.progress "Capturing for Buck genrule compatibility...@.";
JMain.from_arguments path
| Clang (compiler, prog, args) ->
L.progress "Capturing in make/cc mode...@.";
Clang.capture compiler ~prog ~args
| ClangCompilationDB db_files ->
L.progress "Capturing using compilation database...@.";
capture_with_compilation_database ~changed_files db_files
| Javac (compiler, prog, args) ->
L.progress "Capturing in javac mode...@.";
Javac.capture compiler ~prog ~args
| Maven (prog, args) ->
L.progress "Capturing in maven mode...@.";
Maven.capture ~prog ~args
| PythonCapture (build_system, build_cmd) ->
L.progress "Capturing in %s mode...@." (string_of_build_system build_system);
let in_buck_mode = equal_build_system build_system BBuck in
let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in
let args =
List.rev_append Config.anon_args (
["--analyzer";
List.Assoc.find_exn ~equal:Config.equal_analyzer
(List.map ~f:(fun (n,a) -> (a,n)) Config.string_to_analyzer) Config.analyzer] @
(match Config.blacklist with
| Some s when in_buck_mode -> ["--blacklist-regex"; s]
| _ -> []) @
(if not Config.create_harness then [] else
["--android-harness"]) @
(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 not Config.debug_exceptions then [] else
["--debug-exceptions"]) @
(if Config.filtering then [] else
["--no-filtering"]) @
(if not Config.flavors || not in_buck_mode then [] else
["--use-flavors"]) @
"-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.reactive_mode then [] else
["--reactive"]) @
"--out" :: Config.results_dir ::
(match Config.xcode_developer_dir with None -> [] | Some d ->
["--xcode-developer-dir"; d]) @
"--" ::
if in_buck_mode && Config.flavors then
(* 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;
Buck.add_flavors_to_buck_command build_cmd
else build_cmd
) in
run_command ~prog:infer_py ~args
(function
| Result.Error (`Exit_non_zero exit_code) ->
if Int.equal exit_code Config.infer_py_argparse_error_exit_code then
(* swallow infer.py argument parsing error *)
Config.print_usage_exit ()
| _ ->
()
)
| XcodeXcpretty (prog, args) ->
L.progress "Capturing using xcodebuild and xcpretty...@.";
check_xcpretty ();
let json_cdb =
CaptureCompilationDatabase.get_compilation_database_files_xcodebuild ~prog ~args in
capture_with_compilation_database ~changed_files json_cdb
let run_parallel_analysis ~changed_files =
let multicore_dir = Config.results_dir ^/ Config.multicore_dir_name in
Utils.rmtree multicore_dir ;
Unix.mkdir_p multicore_dir ;
InferAnalyze.main ~changed_files ~makefile:(multicore_dir ^/ "Makefile") ;
run_command
~prog:"make" ~args:(
"-C" :: multicore_dir ::
"-k" ::
"-j" :: (string_of_int Config.jobs) ::
(Option.value_map ~f:(fun l -> ["-l"; string_of_float l]) ~default:[] Config.load_average) @
(if Config.debug_mode then [] else ["-s"])
) (fun _ -> ())
let execute_analyze ~changed_files =
if Int.equal Config.jobs 1 || Config.cluster_cmdline <> None then
InferAnalyze.main ~changed_files ~makefile:""
else
run_parallel_analysis ~changed_files
let report () =
let report_csv =
if Config.buck_cache_mode then None else Some (Config.results_dir ^/ "report.csv") in
let report_json = Some (Config.results_dir ^/ "report.json") in
InferPrint.main ~report_csv ~report_json ;
(* Post-process the report according to the user config. By default, calls report.py to create a
human-readable report.
Do not bother calling the report hook when called from within Buck or in quiet mode. *)
match Config.quiet || Config.buck_cache_mode, Config.report_hook with
| true, _
| false, None ->
()
| false, Some prog ->
let if_some key opt args = match opt with None -> args | Some arg -> key :: arg :: args in
let if_true key opt args = if not opt then args else key :: args in
let args =
if_some "--issues-csv" report_csv @@
if_some "--issues-json" report_json @@
if_some "--issues-txt" Config.bugs_txt @@
if_true "--pmd-xml" Config.pmd_xml [
"--project-root"; Config.project_root;
"--results-dir"; Config.results_dir
] in
if is_error (Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())) then
L.external_error
"** Error running the reporting script:@\n** %s %s@\n** See error above@."
prog (String.concat ~sep:" " args)
let analyze_and_report ~changed_files driver_mode =
let should_analyze, should_report = match driver_mode, Config.analyzer with
| PythonCapture (BBuck, _), _ ->
(* In Buck mode when compilation db is not used, analysis is invoked either from capture or
a separate Analyze invocation is necessary, depending on the buck flavor used. *)
false, false
| _ when Config.maven ->
(* Called from Maven, only do capture. *)
false, false
| _, (CaptureOnly | CompileOnly) ->
false, false
| _, (BiAbduction | Checkers | Crashcontext | Eradicate) ->
true, true
| _, Linters ->
false, true in
if (should_analyze || should_report) &&
(((Sys.file_exists Config.captured_dir) <> `Yes) ||
check_captured_empty driver_mode) then (
L.user_error "There was nothing to analyze.@\n@." ;
) else if should_analyze then
execute_analyze ~changed_files;
if should_report && Config.report then report ()
(** as the Config.fail_on_bug flag mandates, exit with error when an issue is reported *)
let fail_on_issue_epilogue () =
let issues_json = DB.Results_dir.(path_to_filename Abs_root ["report.json"])
|> DB.filename_to_string in
match Utils.read_file issues_json with
| Ok lines ->
let issues = Jsonbug_j.report_of_string @@ String.concat ~sep:"" lines in
if issues <> [] then exit Config.fail_on_issue_exit_code
| Error error ->
L.internal_error "Failed to read report file '%s': %s@." issues_json error ;
()
let log_infer_args driver_mode =
L.environment_info "INFER_ARGS = %s@\n"
(Option.value (Sys.getenv CLOpt.args_env_var) ~default:"<not found>");
List.iter ~f:(L.environment_info "anon arg: %s@\n") Config.anon_args;
List.iter ~f:(L.environment_info "rest arg: %s@\n") Config.rest;
L.environment_info "Project root = %s@\n" Config.project_root;
L.environment_info "CWD = %s@\n" (Sys.getcwd ());
L.environment_info "Driver mode:@\n%a@." pp_driver_mode driver_mode
let assert_supported_mode required_analyzer requested_mode_string =
let analyzer_enabled = match required_analyzer with
| `Clang -> Version.clang_enabled
| `Java -> Version.java_enabled
| `Xcode -> Version.clang_enabled && Version.xcode_enabled in
if not analyzer_enabled then
let analyzer_string = match required_analyzer with
| `Clang -> "clang"
| `Java -> "java"
| `Xcode -> "clang and xcode" in
failwithf
"Unsupported build mode: %s@\nInfer was built with %s analyzers disabled.@ Please rebuild \
infer with %s enabled.@."
requested_mode_string analyzer_string analyzer_string
let assert_supported_build_system build_system = match build_system with
| BAnt | BGradle | BJava | BJavac | BMvn ->
string_of_build_system build_system
|> assert_supported_mode `Java
| BClang | BMake | BNdk ->
string_of_build_system build_system
|> assert_supported_mode `Clang
| BXcode ->
string_of_build_system build_system
|> assert_supported_mode `Xcode
| BBuck ->
let (analyzer, build_string) =
if Config.flavors then
(`Clang, "buck with flavors")
else if Option.is_some Config.buck_compilation_database then
(`Clang, "buck compilation database")
else (
if Config.reactive_mode then
L.user_error
"WARNING: The reactive analysis mode is not compatible with the Buck integration for \
Java";
(`Java, string_of_build_system build_system)
) in
assert_supported_mode analyzer build_string
| BAnalyze ->
()
let driver_mode_of_build_cmd build_cmd =
match build_cmd with
| [] ->
if not (List.is_empty !Config.clang_compilation_dbs) then (
assert_supported_mode `Clang "clang compilation database";
ClangCompilationDB !Config.clang_compilation_dbs
) else
Analyze
| prog :: args ->
let build_system = build_system_of_exe_name (Filename.basename prog) in
assert_supported_build_system build_system;
match build_system_of_exe_name (Filename.basename prog) with
| BAnalyze ->
CLOpt.warnf
"WARNING: `infer -- analyze` is deprecated; \
use the `infer analyze` subcommand instead@.";
Analyze
| BBuck when Option.is_some Config.buck_compilation_database ->
BuckCompilationDB (prog, List.append args (List.rev Config.buck_build_args))
| BClang ->
Clang (Clang.Clang, prog, args)
| BMake ->
Clang (Clang.Make, prog, args)
| BJava ->
Javac (Javac.Java, prog, args)
| BJavac ->
Javac (Javac.Javac, prog, args)
| BMvn ->
Maven (prog, args)
| BXcode when Config.xcpretty ->
XcodeXcpretty (prog, args)
| BAnt | BBuck | BGradle | BNdk | BXcode as build_system ->
PythonCapture (build_system, build_cmd)
let get_driver_mode () =
match Config.generated_classes with
| _ when Config.maven ->
(* infer is pretending to be javac in the Maven integration *)
let build_args = match Array.to_list Sys.argv with
| _::args -> args
| [] -> [] in
Javac (Javac.Javac, "javac", build_args)
| Some path ->
assert_supported_mode `Java "Buck genrule";
BuckGenrule path
| None ->
driver_mode_of_build_cmd (List.rev Config.rest)
let mode_from_command_line = lazy (
match Config.generated_classes with
| _ when Config.maven ->
(* infer is pretending to be javac in the Maven integration *)
let build_args = match Array.to_list Sys.argv with
| _::args -> args
| [] -> [] in
Javac (Javac.Javac, "javac", build_args)
| Some path ->
assert_supported_mode `Java "Buck genrule";
BuckGenrule path
| None ->
driver_mode_of_build_cmd (List.rev Config.rest)
)
let run_prologue driver_mode =
if CLOpt.is_originator then L.environment_info "%a@\n" Config.pp_version () ;
if Config.debug_mode || Config.stats_mode then log_infer_args driver_mode ;
if Config.dump_duplicate_symbols then reset_duplicates_file ();
(* infer might be called from a Makefile and itself uses `make` to run the analysis in parallel,
but cannot communicate with the parent make command. Since infer won't interfere with them
anyway, pretend that we are not called from another make to prevent make falling back to a
mono-threaded execution. *)
Unix.unsetenv "MAKEFLAGS";
register_perf_stats_report () ;
if not Config.buck_cache_mode then touch_start_file_unless_continue () ;
()
let run_epilogue driver_mode =
if CLOpt.is_originator then (
let in_buck_mode = match driver_mode with | PythonCapture (BBuck, _) -> true | _ -> false in
StatsAggregator.generate_files () ;
if Config.equal_analyzer Config.analyzer Config.Crashcontext then
Crashcontext.crashcontext_epilogue ~in_buck_mode;
if Config.fail_on_bug then
fail_on_issue_epilogue () ;
);
if Config.buck_cache_mode then clean_results_dir ();
()

@ -0,0 +1,44 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
(** entry points for top-level functionalities such as capture under various build systems,
analysis, and reporting *)
type build_system
(** based on the build_system and options passed to infer, we run in different driver modes *)
type mode =
| Analyze
| BuckGenrule of string
| BuckCompilationDB of string * string list
| Clang of Clang.compiler * string * string list
| ClangCompilationDB of [ `Escaped of string | `Raw of string ] list
| Javac of Javac.compiler * string * string list
| Maven of string * string list
| PythonCapture of build_system * string list
| XcodeXcpretty of string * string list
[@@deriving compare]
val equal_driver_mode : mode -> mode -> bool
(** driver mode computed from the command-line arguments and settings in Config *)
val mode_from_command_line : mode Lazy.t
(** prepare the environment for running the given mode *)
val run_prologue : mode -> unit
(** run the capture for the given mode *)
val capture : changed_files:SourceFile.Set.t option -> mode -> unit
(** run the analysis for the given mode *)
val analyze_and_report : changed_files:SourceFile.Set.t option -> mode -> unit
(** cleanup infer-out/ for Buck, generate stats, and generally post-process the results of a run *)
val run_epilogue : mode -> unit

@ -0,0 +1,36 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
let reportdiff ~current_report:current_report_fname ~previous_report:previous_report_fname =
let load_report filename_opt : Jsonbug_t.report =
let empty_report = [] in
Option.value_map
~f:(fun filename -> Jsonbug_j.report_of_string (In_channel.read_all filename))
~default:empty_report filename_opt in
let current_report = load_report current_report_fname in
let previous_report = load_report previous_report_fname in
let diff =
let unfiltered_diff = Differential.of_reports ~current_report ~previous_report in
if Config.filtering then
let file_renamings = match Config.file_renamings with
| Some f -> DifferentialFilters.FileRenamings.from_json_file f
| None -> DifferentialFilters.FileRenamings.empty in
let interesting_paths = Option.map ~f:(fun fname ->
List.map ~f:(SourceFile.create ~warn_on_error:false) (In_channel.read_lines fname))
Config.differential_filter_files in
DifferentialFilters.do_filter
unfiltered_diff
file_renamings
~skip_duplicated_types:Config.skip_duplicated_types
~interesting_paths
else unfiltered_diff in
let out_path = Config.results_dir ^/ "differential" in
Unix.mkdir_p out_path;
Differential.to_files diff out_path

@ -0,0 +1,11 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
val reportdiff : current_report:string option -> previous_report:string option -> unit
Loading…
Cancel
Save