Add support for modeling @Expensive methods using .inferconfig

Summary:public
Use the configuration file .inferconfig to model the library method that are considered expensive

Reviewed By: cristianoc

Differential Revision: D3045288

fb-gh-sync-id: e58d85c
shipit-source-id: e58d85c
master
jrm 9 years ago committed by Facebook Github Bot 3
parent f4a0f2781a
commit cc4fcd6837

@ -268,6 +268,8 @@ class AnalyzerWrapper(object):
# to be reported # to be reported
infer_options += ['-allow_specs_cleanup'] infer_options += ['-allow_specs_cleanup']
infer_options += ['-inferconfig_home', os.getcwd()]
if self.args.analyzer == config.ANALYZER_ERADICATE: if self.args.analyzer == config.ANALYZER_ERADICATE:
infer_options += ['-eradicate'] infer_options += ['-eradicate']
elif self.args.analyzer == config.ANALYZER_CHECKERS: elif self.args.analyzer == config.ANALYZER_CHECKERS:

@ -46,6 +46,13 @@ let proc_stats_filename = "proc_stats.json"
let global_tenv_filename = "global.tenv" let global_tenv_filename = "global.tenv"
(** Name of the infer configuration file *)
let inferconfig_file = ".inferconfig"
let inferconfig_home : string option ref = ref None
let suppress_warnings_annotations : string option ref = ref None
(** List of paths to the directories containing specs for library functions. *) (** List of paths to the directories containing specs for library functions. *)
let specs_library = ref [] let specs_library = ref []

@ -7,12 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*) *)
(** Name of the infer configuration file *) module L = Logging
let inferconfig_file = ".inferconfig"
let inferconfig_home = ref None
let suppress_warnings_annotations = ref None
(** Look up a key in a json file containing a list of strings *) (** Look up a key in a json file containing a list of strings *)
let lookup_string_list key json = let lookup_string_list key json =
@ -60,58 +55,8 @@ let is_matching patterns =
with Not_found -> false) with Not_found -> false)
patterns patterns
module FileContainsStringMatcher = struct
type matcher = DB.source_file -> bool
let default_matcher : matcher = fun _ -> false
let file_contains regexp file_in =
let rec loop () =
try
(Str.search_forward regexp (input_line file_in) 0) >= 0
with
| Not_found -> loop ()
| End_of_file -> false in
loop ()
let create_matcher s_patterns =
if s_patterns = [] then
default_matcher
else
let source_map = ref DB.SourceFileMap.empty in
let regexp =
Str.regexp (join_strings "\\|" s_patterns) in
fun source_file ->
try
DB.SourceFileMap.find source_file !source_map
with Not_found ->
try
let file_in = open_in (DB.source_file_to_string source_file) in
let pattern_found = file_contains regexp file_in in
close_in file_in;
source_map := DB.SourceFileMap.add source_file pattern_found !source_map;
pattern_found
with Sys_error _ -> false
end
module type MATCHABLE_JSON = sig
val json_key : string
end
module type Matcher = sig
type matcher = DB.source_file -> Procname.t -> bool
val load_matcher : string -> matcher
end
module FileOrProcMatcher = functor (M : MATCHABLE_JSON) ->
struct
type matcher = DB.source_file -> Procname.t -> bool
let default_matcher : matcher =
fun _ _ -> false
type method_pattern = { type method_pattern = {
class_name : string; class_name : string;
method_name : string option; method_name : string option;
parameters : (string list) option parameters : (string list) option
@ -129,27 +74,30 @@ struct
| Method_pattern of Config.language * method_pattern | Method_pattern of Config.language * method_pattern
| Source_contains of Config.language * string | Source_contains of Config.language * string
let language_of_string = function let language_of_string json_key = function
| "Java" -> Config.Java | "Java" -> Config.Java
| l -> failwith ("Inferconfig JSON key " ^ M.json_key ^ " not supported for language " ^ l) | l -> failwith ("Inferconfig JSON key " ^ json_key ^ " not supported for language " ^ l)
let detect_language assoc = let detect_language json_key assoc =
let rec loop = function let rec loop = function
| [] -> | [] ->
failwith failwith
("No language found for " ^ M.json_key ^ " in " ^ inferconfig_file) ("No language found for " ^ json_key ^ " in " ^ Config.inferconfig_file)
| (key, `String s) :: _ when key = "language" -> | (key, `String s) :: _ when key = "language" ->
language_of_string s language_of_string json_key s
| _:: tl -> loop tl in | _:: tl -> loop tl in
loop assoc loop assoc
let detect_pattern assoc = (* Detect the kind of pattern, method pattern or pattern based on the content of the source file.
let language = detect_language assoc in Detecting the kind of patterns in a first step makes it easier to parse the parts of the
let is_method_pattern key = IList.exists (string_equal key) ["class"; "method"] pattern in a second step *)
and is_source_contains key = IList.exists (string_equal key) ["source_contains"] in let detect_pattern json_key assoc =
let rec loop = function let language = detect_language json_key assoc in
| [] -> let is_method_pattern key = IList.exists (string_equal key) ["class"; "method"]
failwith ("Unknown pattern for " ^ M.json_key ^ " in " ^ inferconfig_file) and is_source_contains key = IList.exists (string_equal key) ["source_contains"] in
let rec loop = function
| [] ->
failwith ("Unknown pattern for " ^ json_key ^ " in " ^ Config.inferconfig_file)
| (key, _) :: _ when is_method_pattern key -> | (key, _) :: _ when is_method_pattern key ->
Method_pattern (language, default_method_pattern) Method_pattern (language, default_method_pattern)
| (key, _) :: _ when is_source_contains key -> | (key, _) :: _ when is_source_contains key ->
@ -157,7 +105,8 @@ struct
| _:: tl -> loop tl in | _:: tl -> loop tl in
loop assoc loop assoc
let create_pattern (assoc : (string * Yojson.Basic.json) list) = (* Translate a JSON entry into a matching pattern *)
let create_pattern json_key (assoc : (string * Yojson.Basic.json) list) =
let collect_params l = let collect_params l =
let collect accu = function let collect accu = function
| `String s -> s:: accu | `String s -> s:: accu
@ -176,22 +125,96 @@ struct
IList.fold_left loop default_method_pattern assoc IList.fold_left loop default_method_pattern assoc
and create_string_contains assoc = and create_string_contains assoc =
let loop sc = function let loop sc = function
| (key, `String pattern) when key = "source_contains" -> pattern | (key, `String pattern) when key = "source_contains" -> pattern
| (key, _) when key = "language" -> sc | (key, _) when key = "language" -> sc
| _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in | _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in
IList.fold_left loop default_source_contains assoc in IList.fold_left loop default_source_contains assoc in
match detect_pattern assoc with match detect_pattern json_key assoc with
| Method_pattern (language, _) -> | Method_pattern (language, _) ->
Method_pattern (language, create_method_pattern assoc) Method_pattern (language, create_method_pattern assoc)
| Source_contains (language, _) -> | Source_contains (language, _) ->
Source_contains (language, create_string_contains assoc) Source_contains (language, create_string_contains assoc)
let rec translate accu (json : Yojson.Basic.json) : pattern list = (* Translate all the JSON entries into matching patterns *)
match json with let rec translate json_key accu (json : Yojson.Basic.json) : pattern list =
| `Assoc l -> (create_pattern l):: accu match json with
| `List l -> IList.fold_left translate accu l | `Assoc l -> (create_pattern json_key l):: accu
| `List l -> IList.fold_left (translate json_key) accu l
| _ -> assert false | _ -> assert false
(* Creates a list of matching patterns for the given inferconfig file *)
let load_patterns json_key inferconfig =
let found =
Yojson.Basic.Util.filter_member
json_key
[Yojson.Basic.from_file inferconfig] in
IList.fold_left (translate json_key) [] found
(* Check if a proc name is matching the name given as string *)
let match_method language proc_name method_name =
not (SymExec.function_is_builtin proc_name) &&
match language with
| Config.Java ->
Procname.java_get_method proc_name = method_name
| Config.C_CPP ->
Procname.c_get_method proc_name = method_name
(* Module to create matcher based on strings present in the source file *)
module FileContainsStringMatcher = struct
type matcher = DB.source_file -> bool
let default_matcher : matcher = fun _ -> false
let file_contains regexp file_in =
let rec loop () =
try
(Str.search_forward regexp (input_line file_in) 0) >= 0
with
| Not_found -> loop ()
| End_of_file -> false in
loop ()
let create_matcher s_patterns =
if s_patterns = [] then
default_matcher
else
let source_map = ref DB.SourceFileMap.empty in
let regexp =
Str.regexp (join_strings "\\|" s_patterns) in
fun source_file ->
try
DB.SourceFileMap.find source_file !source_map
with Not_found ->
try
let file_in = open_in (DB.source_file_to_string source_file) in
let pattern_found = file_contains regexp file_in in
close_in file_in;
source_map := DB.SourceFileMap.add source_file pattern_found !source_map;
pattern_found
with Sys_error _ -> false
end
module type MATCHABLE_JSON = sig
val json_key : string
end
module type Matcher = sig
type matcher = DB.source_file -> Procname.t -> bool
val load_matcher : string -> matcher
end
(* Module to create matcher based on source file names or class names and method names *)
module FileOrProcMatcher = functor (M : MATCHABLE_JSON) ->
struct
type matcher = DB.source_file -> Procname.t -> bool
let default_matcher : matcher =
fun _ _ -> false
let create_method_matcher m_patterns = let create_method_matcher m_patterns =
if m_patterns = [] then if m_patterns = [] then
default_matcher default_matcher
@ -234,20 +257,12 @@ struct
let load_matcher inferconfig = let load_matcher inferconfig =
if Sys.file_exists inferconfig then if Sys.file_exists inferconfig then
try create_file_matcher (load_patterns M.json_key inferconfig)
let patterns = else
let found = default_matcher
Yojson.Basic.Util.filter_member
M.json_key
[Yojson.Basic.from_file inferconfig] in
IList.fold_left translate [] found in
create_file_matcher patterns
with Sys_error _ ->
default_matcher
else default_matcher
(*
let pp_pattern fmt pattern = let _pp_pattern fmt pattern =
let pp_string fmt s = let pp_string fmt s =
Format.fprintf fmt "%s" s in Format.fprintf fmt "%s" s in
let pp_option pp_value fmt = function let pp_option pp_value fmt = function
@ -272,9 +287,30 @@ struct
| Source_contains (language, sc) -> | Source_contains (language, sc) ->
Format.fprintf fmt "Source contains (%s) {\n%a}\n" Format.fprintf fmt "Source contains (%s) {\n%a}\n"
(Config.string_of_language language) pp_source_contains sc (Config.string_of_language language) pp_source_contains sc
*)
end (* of module FileOrProcMatcher *) end (* of module FileOrProcMatcher *)
(* Module to create patterns that will match all overriding methods in the pattern *)
module OverridesMatcher = functor (M : MATCHABLE_JSON) ->
struct
type matcher = (string -> bool) -> Procname.t -> bool
let default_matcher _ _ = false
let load_matcher inferconfig =
if Sys.file_exists inferconfig then
fun is_subtype proc_name ->
let is_matching = function
| Method_pattern (language, mp) ->
is_subtype mp.class_name
&& Option.map_default (match_method language proc_name) false mp.method_name
| _ -> failwith "Expecting method pattern" in
IList.exists is_matching (load_patterns M.json_key inferconfig)
else
default_matcher
end
module NeverReturnNull = FileOrProcMatcher(struct module NeverReturnNull = FileOrProcMatcher(struct
let json_key = "never_returning_null" let json_key = "never_returning_null"
@ -288,10 +324,15 @@ module SkipTranslationMatcher = FileOrProcMatcher(struct
let json_key = "skip_translation" let json_key = "skip_translation"
end) end)
module ModeledExpensiveMatcher = OverridesMatcher(struct
let json_key = "modeled_expensive"
end)
let inferconfig () = match !inferconfig_home with let inferconfig () =
| Some dir -> Filename.concat dir inferconfig_file match !Config.inferconfig_home with
| None -> inferconfig_file | Some dir -> Filename.concat dir Config.inferconfig_file
| None -> Config.inferconfig_file
let load_filters analyzer = let load_filters analyzer =
let inferconfig_file = inferconfig () in let inferconfig_file = inferconfig () in
@ -364,7 +405,7 @@ let test () =
let matching_s = let matching_s =
join_strings ", " join_strings ", "
(IList.map string_of_analyzer matching) in (IList.map string_of_analyzer matching) in
Logging.stderr "%s -> {%s}@." L.stderr "%s -> {%s}@."
(DB.source_file_to_rel_path source_file) (DB.source_file_to_rel_path source_file)
matching_s) matching_s)
(Sys.getcwd ()) (Sys.getcwd ())

@ -7,10 +7,6 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*) *)
val inferconfig_home : string option ref
val suppress_warnings_annotations : string option ref
(** get the path to the .inferconfig file *) (** get the path to the .inferconfig file *)
val inferconfig : unit -> string val inferconfig : unit -> string
@ -47,5 +43,10 @@ module SkipTranslationMatcher : Matcher
module SuppressWarningsMatcher : Matcher module SuppressWarningsMatcher : Matcher
module ModeledExpensiveMatcher : sig
type matcher = (string -> bool) -> Procname.t -> bool
val load_matcher : string -> matcher
end
(** Load the config file and list the files to report on *) (** Load the config file and list the files to report on *)
val test: unit -> unit val test: unit -> unit

@ -176,7 +176,7 @@ let arg_desc =
"setup the analyzer for the path filtering" "setup the analyzer for the path filtering"
; ;
"-inferconfig_home", "-inferconfig_home",
Arg.String (fun s -> Inferconfig.inferconfig_home := Some s), Arg.String (fun s -> Config.inferconfig_home := Some s),
Some "dir", Some "dir",
"Path to the .inferconfig file" "Path to the .inferconfig file"
; ;

@ -667,35 +667,47 @@ let base_arg_desc =
Arg.String (fun s -> Config.results_dir := s), Arg.String (fun s -> Config.results_dir := s),
Some "dir", Some "dir",
"set the project results directory (default dir=" ^ Config.default_results_dir ^ ")"; "set the project results directory (default dir=" ^ Config.default_results_dir ^ ")";
"-coverage", "-coverage",
Arg.Unit (fun () -> Config.worklist_mode:= 2), Arg.Unit (fun () -> Config.worklist_mode:= 2),
None, None,
"analysis mode to maximize coverage (can take longer)"; "analysis mode to maximize coverage (can take longer)";
"-lib", "-lib",
Arg.String (fun s -> Config.specs_library := filename_to_absolute s :: !Config.specs_library), Arg.String (fun s -> Config.specs_library := filename_to_absolute s :: !Config.specs_library),
Some "dir", Some "dir",
"add dir to the list of directories to be searched for spec files"; "add dir to the list of directories to be searched for spec files";
"-specs-dir-list-file", "-specs-dir-list-file",
Arg.String (fun s -> Config.specs_library := (read_specs_dir_list_file s) @ !Config.specs_library), Arg.String (fun s -> Config.specs_library := (read_specs_dir_list_file s) @ !Config.specs_library),
Some "file", Some "file",
"add the newline-separated directories listed in <file> to the list of directories to \ "add the newline-separated directories listed in <file> to the list of directories to \
be searched for spec files"; be searched for spec files";
"-models", "-models",
Arg.String (fun s -> Config.add_models (filename_to_absolute s)), Arg.String (fun s -> Config.add_models (filename_to_absolute s)),
Some "zip file", Some "zip file",
"add a zip file containing the models"; "add a zip file containing the models";
"-ziplib", "-ziplib",
Arg.String (fun s -> Config.add_zip_library (filename_to_absolute s)), Arg.String (fun s -> Config.add_zip_library (filename_to_absolute s)),
Some "zip file", Some "zip file",
"add a zip file containing library spec files"; "add a zip file containing library spec files";
"-project_root", "-project_root",
Arg.String (fun s -> Config.project_root := Some (filename_to_absolute s)), Arg.String (fun s -> Config.project_root := Some (filename_to_absolute s)),
Some "dir", Some "dir",
"root directory of the project"; "root directory of the project";
"-infer_cache", "-infer_cache",
Arg.String (fun s -> Config.JarCache.infer_cache := Some (filename_to_absolute s)), Arg.String (fun s -> Config.JarCache.infer_cache := Some (filename_to_absolute s)),
Some "dir", Some "dir",
"Select a directory to contain the infer cache"; "Select a directory to contain the infer cache";
"-inferconfig_home",
Arg.String (fun s -> Config.inferconfig_home := Some s),
Some "dir",
"Path to the .inferconfig file";
] ]
let reserved_arg_desc = let reserved_arg_desc =

@ -29,6 +29,17 @@ let expensive_overrides_unexpensive =
"CHECKERS_EXPENSIVE_OVERRIDES_UNANNOTATED" "CHECKERS_EXPENSIVE_OVERRIDES_UNANNOTATED"
let is_modeled_expensive =
let matcher =
lazy (let config_file = Inferconfig.inferconfig () in
Inferconfig.ModeledExpensiveMatcher.load_matcher config_file) in
fun tenv proc_name ->
not (SymExec.function_is_builtin proc_name) &&
let classname =
Typename.Java.from_string (Procname.java_get_class proc_name) in
(Lazy.force matcher) (AndroidFramework.is_subclass tenv classname) proc_name
let check_attributes check attributes = let check_attributes check attributes =
let annotated_signature = Annotations.get_annotated_signature attributes in let annotated_signature = Annotations.get_annotated_signature attributes in
let ret_annotation, _ = annotated_signature.Annotations.ret in let ret_annotation, _ = annotated_signature.Annotations.ret in
@ -75,24 +86,6 @@ let method_overrides_no_allocation tenv pname =
method_overrides method_is_no_allcation tenv pname method_overrides method_is_no_allcation tenv pname
let is_modeled_expensive tenv pname =
if SymExec.function_is_builtin pname then false
else if Procname.java_get_method pname <> "findViewById" then false
else
let package =
match Procname.java_get_package pname with
| None -> ""
| Some p -> p in
let classname =
Mangled.from_package_class package (Procname.java_get_simple_class pname) in
match Sil.tenv_lookup tenv (Typename.TN_csu (Csu.Class Csu.Java, classname)) with
| None -> false
| Some struct_typ ->
let typ = Sil.Tstruct struct_typ in
AndroidFramework.is_view typ tenv
|| AndroidFramework.is_activity typ tenv
let method_is_expensive tenv pname = let method_is_expensive tenv pname =
is_modeled_expensive tenv pname is_modeled_expensive tenv pname
|| check_method Annotations.ia_is_expensive pname || check_method Annotations.ia_is_expensive pname
@ -239,9 +232,9 @@ let report_allocation_stack pname loc trace stack_str constructor_pname call_loc
Reporting.log_error pname ~loc: (Some loc) ~ltr: (Some final_trace) exn Reporting.log_error pname ~loc: (Some loc) ~ltr: (Some final_trace) exn
let report_call_stack end_of_stack lookup_next_calls report tenv pname pdesc loc calls = let report_call_stack end_of_stack lookup_next_calls report pname pdesc loc calls =
let rec loop visited_pnames (trace, stack_str) (callee_pname, callee_loc) = let rec loop visited_pnames (trace, stack_str) (callee_pname, callee_loc) =
if end_of_stack tenv callee_pname then if end_of_stack callee_pname then
report pname loc trace stack_str callee_pname callee_loc report pname loc trace stack_str callee_pname callee_loc
else else
let next_calls = lookup_next_calls callee_pname in let next_calls = lookup_next_calls callee_pname in
@ -261,14 +254,14 @@ let report_call_stack end_of_stack lookup_next_calls report tenv pname pdesc loc
let report_expensive_calls tenv pname pdesc loc calls = let report_expensive_calls tenv pname pdesc loc calls =
report_call_stack report_call_stack
method_is_expensive lookup_expensive_calls report_expensive_call_stack (method_is_expensive tenv) lookup_expensive_calls
tenv pname pdesc loc calls report_expensive_call_stack pname pdesc loc calls
let report_allocations tenv pname pdesc loc calls = let report_allocations pname pdesc loc calls =
report_call_stack report_call_stack
(fun _ p -> Procname.is_constructor p) lookup_allocations report_allocation_stack Procname.is_constructor lookup_allocations
tenv pname pdesc loc calls report_allocation_stack pname pdesc loc calls
let check_one_procedure tenv pname pdesc = let check_one_procedure tenv pname pdesc =
@ -311,7 +304,7 @@ let check_one_procedure tenv pname pdesc =
if performance_critical then if performance_critical then
report_expensive_calls tenv pname pdesc loc call_summary.Specs.expensive_calls; report_expensive_calls tenv pname pdesc loc call_summary.Specs.expensive_calls;
if no_allocation then if no_allocation then
report_allocations tenv pname pdesc loc call_summary.Specs.allocations report_allocations pname pdesc loc call_summary.Specs.allocations
let callback_performance_checker let callback_performance_checker

@ -52,6 +52,8 @@ val is_android_lib_class : Typename.t -> bool
(** Path to the android.jar file containing real code, not just the method stubs as in the SDK *) (** Path to the android.jar file containing real code, not just the method stubs as in the SDK *)
val non_stub_android_jar : unit -> string val non_stub_android_jar : unit -> string
val is_subclass : Sil.tenv -> Typename.t -> string -> bool
(** [is_exception tenv class_name] checks if class_name is of type java.lang.Exception *) (** [is_exception tenv class_name] checks if class_name is of type java.lang.Exception *)
val is_exception : Sil.tenv -> Typename.t -> bool val is_exception : Sil.tenv -> Typename.t -> bool

@ -11,32 +11,20 @@
open Javalib_pack open Javalib_pack
let suppress_warnings_lookup = ref None let is_suppress_warnings_annotated =
let load_suppress_warnings_lookup () =
let default_matcher = fun _ -> false in
let matcher =
match !Inferconfig.suppress_warnings_annotations with
| Some f ->
(try
let m = Inferconfig.SuppressWarningsMatcher.load_matcher f in
(m DB.source_file_empty)
with Yojson.Json_error _ ->
default_matcher)
| None -> failwith "Local config expected!" in
suppress_warnings_lookup := Some matcher
let is_suppress_warnings_annotated proc_name =
let matcher = let matcher =
let () = lazy
match !suppress_warnings_lookup with (let default_matcher = fun _ -> false in
| None -> match !Config.suppress_warnings_annotations with
load_suppress_warnings_lookup () | Some f ->
| Some _ -> () in (try
Option.get !suppress_warnings_lookup in let m = Inferconfig.SuppressWarningsMatcher.load_matcher f in
matcher proc_name (m DB.source_file_empty)
with Yojson.Json_error _ ->
default_matcher)
| None -> failwith "Local config expected!") in
fun proc_name ->
(Lazy.force matcher) proc_name
let suppress_warnings = let suppress_warnings =

@ -63,7 +63,7 @@ let arg_desc =
"Set the path to the javac verbose output" "Set the path to the javac verbose output"
; ;
"-suppress_warnings_out", "-suppress_warnings_out",
Arg.String (fun s -> Inferconfig.suppress_warnings_annotations := Some s), Arg.String (fun s -> Config.suppress_warnings_annotations := Some s),
Some "Path", Some "Path",
"Path to list of collected @SuppressWarnings annotations" "Path to list of collected @SuppressWarnings annotations"
; ;

@ -18,5 +18,17 @@
"language": "Java", "language": "Java",
"source_contains": "_SHOULD_BE_SKIPPED_" "source_contains": "_SHOULD_BE_SKIPPED_"
} }
],
"modeled_expensive": [
{
"language": "Java",
"class": "android.app.Activity",
"method": "findViewById"
},
{
"language": "Java",
"class": "android.view.View",
"method": "findViewById"
}
] ]
} }

@ -16,6 +16,8 @@ java_library(
) )
out = 'out' out = 'out'
inferconfig_file = '$(location //infer/tests/codetoanalyze/java:inferconfig)'
copy_inferconfig = ' '.join(['cp', inferconfig_file, '$SRCDIR'])
clean_cmd = ' '.join(['rm', '-rf', out]) clean_cmd = ' '.join(['rm', '-rf', out])
classpath = ':'.join([('$(classpath ' + path + ')') for path in dependencies]) classpath = ':'.join([('$(classpath ' + path + ')') for path in dependencies])
infer_cmd = ' '.join([ infer_cmd = ' '.join([
@ -30,7 +32,7 @@ infer_cmd = ' '.join([
'$SRCS', '$SRCS',
]) ])
copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT']) copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT'])
command = ' && '.join([clean_cmd, infer_cmd, copy_cmd]) command = ' && '.join([clean_cmd, copy_inferconfig, infer_cmd, copy_cmd])
genrule( genrule(
name = 'analyze', name = 'analyze',

Loading…
Cancel
Save