Directly handle javac -version option in build command

Summary:
This diff parses the build command args to directly handle the -version
option passed to java and javac, to make the integration with buck more
robust by ensuring that the version and no additional debug logging is
generated for `infer --debug -- javac -version`.

Reviewed By: jeremydubreil

Differential Revision: D4158011

fbshipit-source-id: e7d6b4d
master
Josh Berdine 8 years ago committed by Facebook Github Bot
parent 3dfaa9ed7b
commit cccfad2445

@ -29,27 +29,6 @@ csv.field_size_limit(sys.maxsize)
INFER_ANALYZE_BINARY = 'InferAnalyze' INFER_ANALYZE_BINARY = 'InferAnalyze'
def get_infer_version():
try:
return subprocess.check_output([
utils.get_cmd_in_bin_dir(INFER_ANALYZE_BINARY), '-version'])
except subprocess.CalledProcessError:
utils.stdout('Failed to run {0} binary, exiting'
.format(INFER_ANALYZE_BINARY))
sys.exit(os.EX_UNAVAILABLE)
# https://github.com/python/cpython/blob/aa8ea3a6be22c92e774df90c6a6ee697915ca8ec/Lib/argparse.py
class VersionAction(argparse._VersionAction):
def __call__(self, parser, namespace, values, option_string=None):
# set self.version so that argparse version action knows it
self.version = get_infer_version()
super(VersionAction, self).__call__(parser,
namespace,
values,
option_string)
base_parser = argparse.ArgumentParser(add_help=False) base_parser = argparse.ArgumentParser(add_help=False)
base_group = base_parser.add_argument_group('global arguments') base_group = base_parser.add_argument_group('global arguments')
base_group.add_argument('-o', '--out', metavar='<directory>', base_group.add_argument('-o', '--out', metavar='<directory>',
@ -83,10 +62,6 @@ base_group.add_argument('--android-harness', action='store_true',
help='''[experimental] Create harness to detect bugs help='''[experimental] Create harness to detect bugs
involving the Android lifecycle''') involving the Android lifecycle''')
base_parser.add_argument('-v', '--version',
help='''Print the version of Infer and exit''',
action=VersionAction)
base_group.add_argument('--pmd-xml', base_group.add_argument('--pmd-xml',
action='store_true', action='store_true',
help='''Output issues in (PMD) XML format.''') help='''Output issues in (PMD) XML format.''')

@ -25,7 +25,6 @@ parser = argparse.ArgumentParser()
current_directory = utils.decode(os.getcwd()) current_directory = utils.decode(os.getcwd())
parser.add_argument('-version', action='store_true')
parser.add_argument('-d', dest='classes_out', default=current_directory) parser.add_argument('-d', dest='classes_out', default=current_directory)
@ -67,74 +66,65 @@ class CompilerCall(object):
self.args, self.remaining_args = parser.parse_known_args(arguments) self.args, self.remaining_args = parser.parse_known_args(arguments)
self.verbose_out = None self.verbose_out = None
def get_version(self):
assert self.args.version
return subprocess.check_output(
self.javac_cmd + self.original_arguments,
stderr=subprocess.STDOUT).strip()
def run(self): def run(self):
if self.args.version: javac_args = ['-verbose', '-g']
return subprocess.call(self.javac_cmd + self.original_arguments)
else: if self.args.classes_out is not None:
javac_args = ['-verbose', '-g'] javac_args += ['-d', self.args.classes_out]
if self.args.classes_out is not None: javac_args += self.remaining_args
javac_args += ['-d', self.args.classes_out]
def arg_must_go_on_cli(arg):
javac_args += self.remaining_args # as mandated by javac, argument files must not contain
# arguments
def arg_must_go_on_cli(arg): return arg.startswith('-J') or arg.startswith('@')
# as mandated by javac, argument files must not contain file_args = filter(lambda x: not arg_must_go_on_cli(x), javac_args)
# arguments cli_args = filter(arg_must_go_on_cli, javac_args)
return arg.startswith('-J') or arg.startswith('@')
file_args = filter(lambda x: not arg_must_go_on_cli(x), javac_args) # pass non-special args via a file to avoid blowing up the
cli_args = filter(arg_must_go_on_cli, javac_args) # command line size limit
with tempfile.NamedTemporaryFile(
# pass non-special args via a file to avoid blowing up the mode='w',
# command line size limit prefix='javac_args_',
with tempfile.NamedTemporaryFile( delete=False) as command_line:
mode='w', escaped_args = map(lambda x: '"%s"' % (x.replace('"', '\\"')),
prefix='javac_args_', file_args)
delete=False) as command_line: command_line.write(utils.encode('\n'.join(escaped_args)))
escaped_args = map(lambda x: '"%s"' % (x.replace('"', '\\"')), command_line.write('\n')
file_args) self.command_line_file = command_line.name
command_line.write(utils.encode('\n'.join(escaped_args)))
command_line.write('\n') with tempfile.NamedTemporaryFile(
self.command_line_file = command_line.name mode='w',
suffix='.out',
with tempfile.NamedTemporaryFile( prefix='javac_',
mode='w', delete=False) as file_out:
suffix='.out', self.verbose_out = file_out.name
prefix='javac_',
delete=False) as file_out: command = self.javac_cmd + cli_args + \
self.verbose_out = file_out.name ['@' + str(self.command_line_file)]
try:
command = self.javac_cmd + cli_args + \ subprocess.check_call(command, stderr=file_out)
['@' + str(self.command_line_file)] except subprocess.CalledProcessError:
try: try:
subprocess.check_call(command, stderr=file_out) fallback_command = ['javac'] + cli_args + \
['@' + str(self.command_line_file)]
subprocess.check_call(
fallback_command, stderr=file_out)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
try: error_msg = 'ERROR: failure during compilation ' \
fallback_command = ['javac'] + cli_args + \ + 'command.\nYou can run the failing ' \
['@' + str(self.command_line_file)] + 'compilation command again by ' \
subprocess.check_call( + 'copy-pasting the\nlines below in ' \
fallback_command, stderr=file_out) + 'your terminal:\n\n"""\n' \
except subprocess.CalledProcessError: + 'python <<EOF\n' \
error_msg = 'ERROR: failure during compilation ' \ + 'import subprocess\n' \
+ 'command.\nYou can run the failing ' \ + 'cmd = {}\n' \
+ 'compilation command again by ' \ + 'subprocess.check_call(cmd)\n' \
+ 'copy-pasting the\nlines below in ' \ + 'EOF\n"""\n'
+ 'your terminal:\n\n"""\n' \ failing_cmd = filter(lambda arg: arg != '-verbose',
+ 'python <<EOF\n' \ command)
+ 'import subprocess\n' \ utils.stderr(error_msg.format(failing_cmd))
+ 'cmd = {}\n' \ subprocess.check_call(failing_cmd)
+ 'subprocess.check_call(cmd)\n' \
+ 'EOF\n"""\n'
failing_cmd = filter(lambda arg: arg != '-verbose',
command)
utils.stderr(error_msg.format(failing_cmd))
subprocess.check_call(failing_cmd)
return os.EX_OK return os.EX_OK
@ -144,39 +134,27 @@ class AnalyzerWithFrontendWrapper(analyze.AnalyzerWrapper):
def __init__(self, infer_args, compiler_call): def __init__(self, infer_args, compiler_call):
analyze.AnalyzerWrapper.__init__(self, infer_args) analyze.AnalyzerWrapper.__init__(self, infer_args)
self.javac = compiler_call self.javac = compiler_call
if not self.javac.args.version: if self.javac.original_arguments is None:
if self.javac.original_arguments is None: raise Exception('No javac command detected')
raise Exception('No javac command detected')
def compute_buck_key(self):
javac_version = self.javac.get_version()
infer_version = utils.infer_key(self.args.analyzer)
return '/'.join([javac_version, infer_version])
def start(self): def start(self):
if self.javac.args.version: start_time = time.time()
if self.args.buck:
utils.stderr(self.compute_buck_key()) self._compile()
else: if self.args.analyzer == config.ANALYZER_COMPILE:
return self.javac.run() return os.EX_OK
else:
start_time = time.time() self._run_infer_frontend()
self.timing['capture'] = utils.elapsed_time(start_time)
self._compile() if self.args.analyzer == config.ANALYZER_CAPTURE:
if self.args.analyzer == config.ANALYZER_COMPILE: return os.EX_OK
return os.EX_OK
self.analyze_and_report()
self._run_infer_frontend() self._close()
self.timing['capture'] = utils.elapsed_time(start_time) self.timing['total'] = utils.elapsed_time(start_time)
if self.args.analyzer == config.ANALYZER_CAPTURE: self.save_stats()
return os.EX_OK
return self.stats
self.analyze_and_report()
self._close()
self.timing['total'] = utils.elapsed_time(start_time)
self.save_stats()
return self.stats
def _run_infer_frontend(self): def _run_infer_frontend(self):
infer_cmd = [utils.get_cmd_in_bin_dir('InferJava')] infer_cmd = [utils.get_cmd_in_bin_dir('InferJava')]

@ -1174,17 +1174,17 @@ and verbose_out =
~meta:"file" "" ~meta:"file" ""
and version = and version =
CLOpt.mk_bool ~deprecated:["version"] ~long:"version" let var = ref `None in
~exes:CLOpt.[Toplevel;Analyze;Clang;Java;Print] "Print version information and exit" CLOpt.mk_set var `Full ~deprecated:["version"] ~long:"version"
~exes:CLOpt.[Toplevel;Analyze;Clang;Java;Print]
and version_json = "Print version information and exit" ;
CLOpt.mk_bool ~deprecated:["version_json"] ~long:"version-json" CLOpt.mk_set var `Json ~deprecated:["version_json"] ~long:"version-json"
~exes:CLOpt.[Analyze;Clang;Java;Print] ~exes:CLOpt.[Analyze;Clang;Java;Print]
"Print version json formatted" "Print version information in json format and exit" ;
CLOpt.mk_set var `Vcs ~long:"version-vcs"
and version_vcs = ~exes:CLOpt.[Analyze;Clang;Java;Print]
CLOpt.mk_bool ~long:"version-vcs" "Print version control system commit and exit" ;
~exes:CLOpt.[Analyze;Clang;Java;Print] "Print version control system commit and exit" var
and whole_seconds = and whole_seconds =
CLOpt.mk_bool ~deprecated:["whole_seconds"] ~long:"whole-seconds" CLOpt.mk_bool ~deprecated:["whole_seconds"] ~long:"whole-seconds"
@ -1244,6 +1244,7 @@ let rest =
specs_library := List.rev_append files !specs_library specs_library := List.rev_append files !specs_library
) )
) in ) in
let version_spec = Arg.Unit (fun () -> version := `Javac) in
CLOpt.mk_subcommand CLOpt.mk_subcommand
~exes:CLOpt.[Toplevel] ~exes:CLOpt.[Toplevel]
"Stop argument processing, use remaining arguments as a build command" "Stop argument processing, use remaining arguments as a build command"
@ -1251,7 +1252,8 @@ let rest =
match Filename.basename build_exe with match Filename.basename build_exe with
| "java" | "javac" -> [ | "java" | "javac" -> [
("-classes_out", classes_out_spec, ""); ("-d", classes_out_spec, ""); ("-classes_out", classes_out_spec, ""); ("-d", classes_out_spec, "");
("-classpath", classpath_spec, ""); ("-cp", classpath_spec, "") ("-classpath", classpath_spec, ""); ("-cp", classpath_spec, "");
("-version", version_spec, "")
] ]
| _ -> [] | _ -> []
) )
@ -1284,21 +1286,35 @@ let exe_usage (exe : CLOpt.exe) =
version_string version_string
let post_parsing_initialization () = let post_parsing_initialization () =
F.set_margin !margin ; (match !version with
| `Full ->
if !version then ( (* TODO(11791235) change back to stdout once buck integration is fixed *)
(* TODO(11791235) change back to stdout once buck integration is fixed *) prerr_endline version_string
F.fprintf F.err_formatter "%s@." version_string ; | `Javac when !buck ->
exit 0 (* print buck key *)
); let javac_version =
if !version_json then ( (* stderr contents of build command *)
F.fprintf F.std_formatter "%s@." Version.versionJson ; let chans = Unix.open_process_full (String.concat ~sep:" " (List.rev !rest)) ~env:[||] in
exit 0 let err = String.strip (In_channel.input_all chans.stderr) in
); Unix.close_process_full chans |> ignore;
if !version_vcs then ( err in
F.fprintf F.std_formatter "%s@." Version.commit ; let analyzer_name =
exit 0 IList.assoc (=)
(match !analyzer with Some a -> a | None -> Infer)
(IList.map (fun (n,a) -> (a,n)) string_to_analyzer) in
let infer_version = Version.commit in
F.eprintf "%s/%s/%s@." javac_version analyzer_name infer_version
| `Javac ->
prerr_endline version_string
| `Json ->
print_endline Version.versionJson
| `Vcs ->
print_endline Version.commit
| `None -> ()
); );
if !version <> `None then exit 0;
F.set_margin !margin ;
let set_minor_heap_size nMb = (* increase the minor heap size to speed up gc *) let set_minor_heap_size nMb = (* increase the minor heap size to speed up gc *)
let ctrl = Gc.get () in let ctrl = Gc.get () in

Loading…
Cancel
Save