Reviewed By: ngorogiannis Differential Revision: D20467422 fbshipit-source-id: 2b7cf287dmaster
parent
6504f12029
commit
a464880611
@ -1,203 +0,0 @@
|
||||
#!/usr/bin/env python2.7
|
||||
|
||||
# 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 imp
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
import inferlib
|
||||
from inferlib import analyze, config, utils
|
||||
|
||||
CAPTURE_PACKAGE = 'capture'
|
||||
|
||||
# token that identifies the end of the options for infer and the beginning
|
||||
# of the compilation command
|
||||
CMD_MARKER = '--'
|
||||
|
||||
# insert here the correspondence between module name and the list of
|
||||
# compiler/build-systems it handles.
|
||||
# All supported commands should be listed here
|
||||
MODULE_TO_COMMAND = {
|
||||
'ant': ['ant'],
|
||||
'buck': ['buck'],
|
||||
'gradle': ['gradle', 'gradlew'],
|
||||
'xcodebuild': ['xcodebuild'],
|
||||
'ndk-build': ['ndk-build'],
|
||||
}
|
||||
|
||||
|
||||
def get_commands():
|
||||
"""Return all commands that are supported."""
|
||||
# flatten and dedup the list of commands
|
||||
return set(sum(MODULE_TO_COMMAND.values(), []))
|
||||
|
||||
|
||||
def get_module_name(command):
|
||||
""" Return module that is able to handle the command. None if
|
||||
there is no such module."""
|
||||
for module, commands in MODULE_TO_COMMAND.iteritems():
|
||||
if command in commands:
|
||||
return module
|
||||
return None
|
||||
|
||||
|
||||
def load_module(mod_name):
|
||||
pkg_info = imp.find_module(CAPTURE_PACKAGE, inferlib.__path__)
|
||||
imported_pkg = imp.load_module(CAPTURE_PACKAGE, *pkg_info)
|
||||
# load the requested module (e.g. make)
|
||||
mod_file, mod_path, mod_descr = \
|
||||
imp.find_module(mod_name, imported_pkg.__path__)
|
||||
try:
|
||||
return imp.load_module(
|
||||
'{pkg}.{mod}'.format(pkg=imported_pkg.__name__, mod=mod_name),
|
||||
mod_file, mod_path, mod_descr)
|
||||
finally:
|
||||
if mod_file:
|
||||
mod_file.close()
|
||||
|
||||
|
||||
def split_args_to_parse():
|
||||
sys_argv = map(utils.decode, sys.argv)
|
||||
dd_index = \
|
||||
sys_argv.index(CMD_MARKER) if CMD_MARKER in sys_argv else len(sys_argv)
|
||||
cmd_raw = sys_argv[dd_index + 1:]
|
||||
return (sys_argv[1:dd_index], cmd_raw)
|
||||
|
||||
|
||||
class FailSilentlyArgumentParser(argparse.ArgumentParser):
|
||||
'''We want to leave the handling of printing usage messages to the
|
||||
OCaml code. To do so, swallow error messages from ArgumentParser
|
||||
and exit with a special error code that infer.ml looks for.
|
||||
'''
|
||||
|
||||
def error(self, message):
|
||||
utils.stderr(message)
|
||||
utils.stderr('')
|
||||
exit(22) # in sync with infer.ml
|
||||
|
||||
def print_help(self, file=None):
|
||||
exit(22) # in sync with infer.ml
|
||||
|
||||
|
||||
def create_argparser(parents=[]):
|
||||
parser = FailSilentlyArgumentParser(
|
||||
parents=[analyze.infer_parser] + parents,
|
||||
add_help=False,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
group = parser.add_argument_group(
|
||||
'supported compiler/build-system commands')
|
||||
|
||||
supported_commands = ', '.join(get_commands())
|
||||
group.add_argument(
|
||||
CMD_MARKER,
|
||||
metavar='<cmd>',
|
||||
dest='nullarg',
|
||||
default=None,
|
||||
help=('Command to run the compiler/build-system. '
|
||||
'Supported build commands (run `infer --help -- <cmd_name>` for '
|
||||
'extra help, e.g. `infer --help -- javac`): {}'.format(
|
||||
supported_commands)),
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
class IgnoreFailuresArgumentParser(argparse.ArgumentParser):
|
||||
def error(self, message):
|
||||
pass
|
||||
|
||||
def print_help(self, file=None):
|
||||
pass
|
||||
|
||||
def main():
|
||||
to_parse, cmd = split_args_to_parse()
|
||||
# first pass to see if a capture module is forced
|
||||
initial_argparser = IgnoreFailuresArgumentParser(
|
||||
parents=[analyze.infer_parser],
|
||||
add_help=False,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
initial_args = initial_argparser.parse_args(to_parse)
|
||||
|
||||
# get the module name (if any), then load it
|
||||
capture_module_name = None
|
||||
if initial_args.force_integration is not None:
|
||||
capture_module_name = initial_args.force_integration
|
||||
elif len(cmd) > 0:
|
||||
capture_module_name = os.path.basename(cmd[0])
|
||||
mod_name = get_module_name(capture_module_name)
|
||||
imported_module = None
|
||||
if mod_name:
|
||||
# There is module that supports the command
|
||||
imported_module = load_module(mod_name)
|
||||
|
||||
# get the module's argparser and merge it with the global argparser
|
||||
module_argparser = []
|
||||
if imported_module:
|
||||
module_argparser.append(
|
||||
imported_module.create_argparser(capture_module_name)
|
||||
)
|
||||
global_argparser = create_argparser(module_argparser)
|
||||
|
||||
args = global_argparser.parse_args(to_parse)
|
||||
|
||||
if imported_module is not None:
|
||||
utils.configure_logging(args)
|
||||
try:
|
||||
logging.info('output of locale.getdefaultlocale(): %s',
|
||||
str(locale.getdefaultlocale()))
|
||||
except (locale.Error, ValueError) as e:
|
||||
logging.info('locale.getdefaultlocale() failed with exception: %s',
|
||||
str(e))
|
||||
logging.info('encoding we chose in the end: %s',
|
||||
config.CODESET)
|
||||
logging.info('Running command %s',
|
||||
' '.join(map(utils.decode, sys.argv)))
|
||||
logging.info('Path to infer script %s (%s)', utils.decode(__file__),
|
||||
os.path.realpath(utils.decode(__file__)))
|
||||
logging.info('Platform: %s', utils.decode(platform.platform()))
|
||||
|
||||
def log_getenv(k):
|
||||
v = os.getenv(k)
|
||||
if v is not None:
|
||||
v = utils.decode(v)
|
||||
else:
|
||||
v = '<NOT SET>'
|
||||
logging.info('%s=%s', k, v)
|
||||
|
||||
log_getenv('PATH')
|
||||
log_getenv('SHELL')
|
||||
log_getenv('PWD')
|
||||
|
||||
capture_exitcode = imported_module.gen_instance(args, cmd).capture()
|
||||
if capture_exitcode != os.EX_OK:
|
||||
logging.error('Error during capture phase, exiting')
|
||||
exit(capture_exitcode)
|
||||
logging.info('Capture phase was successful')
|
||||
elif capture_module_name is not None:
|
||||
# There was a command, but it's not supported
|
||||
utils.stderr('Command "{cmd}" not recognised'
|
||||
.format(cmd='' if capture_module_name is None
|
||||
else capture_module_name))
|
||||
global_argparser.print_help()
|
||||
sys.exit(1)
|
||||
else:
|
||||
global_argparser.print_help()
|
||||
sys.exit(os.EX_OK)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,62 +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 multiprocessing
|
||||
|
||||
|
||||
from . import config, utils
|
||||
|
||||
base_parser = argparse.ArgumentParser(add_help=False)
|
||||
base_group = base_parser.add_argument_group('global arguments')
|
||||
base_group.add_argument('-o', '--out', metavar='<directory>',
|
||||
default=utils.encode(config.DEFAULT_INFER_OUT),
|
||||
dest='infer_out',
|
||||
type=utils.decode,
|
||||
action=utils.AbsolutePathAction,
|
||||
help='Set the Infer results directory')
|
||||
base_group.add_argument('--continue', dest="continue_capture",
|
||||
action='store_true',
|
||||
help='''Continue the capture, do not delete previous
|
||||
results''')
|
||||
base_group.add_argument('-g', '--debug', action='store_true',
|
||||
help='Generate all debugging information')
|
||||
base_group.add_argument('-nf', '--no-filtering', action='store_true',
|
||||
help='''Also show the results from the experimental
|
||||
checks. Warning: some checks may contain many false
|
||||
alarms''')
|
||||
base_group.add_argument('--force-integration', metavar='<build system>',
|
||||
type=utils.decode,
|
||||
help='Force the integration to be used regardless of \
|
||||
the build command that is passed')
|
||||
base_group.add_argument('--pmd-xml',
|
||||
action='store_true',
|
||||
help='''Output issues in (PMD) XML format.''')
|
||||
base_group.add_argument('--quiet', action='store_true',
|
||||
help='Silence console output.')
|
||||
|
||||
|
||||
infer_parser = argparse.ArgumentParser(parents=[base_parser])
|
||||
infer_group = infer_parser.add_argument_group('backend arguments')
|
||||
infer_group.add_argument('-pr', '--project-root',
|
||||
dest='project_root',
|
||||
help='Location of the project root '
|
||||
'(default is current directory)')
|
||||
infer_group.add_argument('-j', '--multicore', metavar='n', type=int,
|
||||
default=multiprocessing.cpu_count(),
|
||||
dest='multicore', help='Set the number of cores to '
|
||||
'be used for the analysis (default uses all cores)')
|
||||
infer_group.add_argument('-l', '--load-average', metavar='<float>', type=float,
|
||||
help='Specifies that no new jobs (commands) should '
|
||||
'be started if there are others jobs running and the '
|
||||
'load average is at least <float>.')
|
||||
|
||||
infer_group.add_argument('--java-jar-compiler',
|
||||
metavar='<file>')
|
@ -1,4 +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.
|
@ -1,217 +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.
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import util
|
||||
import tempfile
|
||||
|
||||
MODULE_NAME = __name__
|
||||
MODULE_DESCRIPTION = '''Run analysis of code built with a command like:
|
||||
gradle [options] [task]
|
||||
|
||||
Analysis examples:
|
||||
infer -- gradle build
|
||||
infer -- ./gradlew build'''
|
||||
LANG = ['java']
|
||||
|
||||
|
||||
def gen_instance(*args):
|
||||
return GradleCapture(*args)
|
||||
|
||||
|
||||
# This creates an empty argparser for the module, which provides only
|
||||
# description/usage information and no arguments.
|
||||
create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME)
|
||||
|
||||
|
||||
def extract_filepath(parts):
|
||||
size = len(parts)
|
||||
pos = size - 1
|
||||
while pos >= 0:
|
||||
path = ' '.join(itertools.islice(parts, pos, None))
|
||||
if os.path.isfile(path):
|
||||
return parts[:pos], path
|
||||
pos -= 1
|
||||
return parts, None
|
||||
|
||||
|
||||
def pop(the_list):
|
||||
if len(the_list) > 0:
|
||||
return the_list.pop()
|
||||
return None
|
||||
|
||||
|
||||
def extract_argfiles_from_rev(javac_arguments):
|
||||
"""Extract class names and @argfiles from the reversed list."""
|
||||
# Reverse the list, so it's in a natural order now
|
||||
javac_arguments = list(reversed(javac_arguments))
|
||||
java_opts = []
|
||||
saved = []
|
||||
java_arg = pop(javac_arguments)
|
||||
while java_arg is not None:
|
||||
if java_arg.startswith('@'):
|
||||
# Probably got an @argfile
|
||||
path = ' '.join([java_arg[1:]] + saved)
|
||||
if os.path.isfile(path):
|
||||
java_opts.insert(0, '@' + path)
|
||||
saved = []
|
||||
else:
|
||||
# @ at the middle of the path
|
||||
saved.insert(0, java_arg)
|
||||
else:
|
||||
# Either a class name or a part of the @argfile path
|
||||
saved.insert(0, java_arg)
|
||||
java_arg = pop(javac_arguments)
|
||||
|
||||
# Only class names left
|
||||
java_opts[0:0] = saved
|
||||
|
||||
return java_opts
|
||||
|
||||
|
||||
# Please run the doctests using:
|
||||
# $ python -m doctest -v gradle.py
|
||||
def extract_all(javac_arguments):
|
||||
"""Extract Java filenames and Javac options from the Javac arguments.
|
||||
|
||||
>>> os.path.isfile = lambda s: s[1:].startswith('path/to/')
|
||||
>>> extract_all([])
|
||||
{'files': [], 'opts': []}
|
||||
>>> extract_all(['-opt1', 'optval1', '/path/to/1.java'])
|
||||
{'files': ['/path/to/1.java'], 'opts': ['-opt1', 'optval1']}
|
||||
>>> extract_all(['-opt1', 'optval1', '/path/to/a', 'b/1.java'])
|
||||
{'files': ['/path/to/a b/1.java'], 'opts': ['-opt1', 'optval1']}
|
||||
>>> extract_all(['-opt1', 'opt', 'val1', '/path/to/1.java'])
|
||||
{'files': ['/path/to/1.java'], 'opts': ['-opt1', 'opt val1']}
|
||||
>>> extract_all(['-opt1', '/path/to/a', 'b/c', 'd/1.java', '-opt2'])
|
||||
{'files': ['/path/to/a b/c d/1.java'], 'opts': ['-opt1', '-opt2']}
|
||||
>>> extract_all(['-opt1', 'optval1', '-path/to/1.java'])
|
||||
{'files': ['-path/to/1.java'], 'opts': ['-opt1', 'optval1']}
|
||||
>>> extract_all(['-opt1', 'optval1', '/path/to/', '-1.java'])
|
||||
{'files': ['/path/to/ -1.java'], 'opts': ['-opt1', 'optval1']}
|
||||
>>> extract_all(['undef1', 'undef2'])
|
||||
{'files': [], 'opts': ['undef1', 'undef2']}
|
||||
>>> extract_all(['-o', '/path/to/1.java', 'cls.class', '@/path/to/1'])
|
||||
{'files': ['/path/to/1.java'], 'opts': ['-o', 'cls.class', '@/path/to/1']}
|
||||
>>> extract_all(['-opt1', 'optval1', '/path/to/1.java', 'cls.class'])
|
||||
{'files': ['/path/to/1.java'], 'opts': ['-opt1', 'optval1', 'cls.class']}
|
||||
>>> extract_all(['cls.class', '@/path/to/a', 'b.txt'])
|
||||
{'files': [], 'opts': ['cls.class', '@/path/to/a b.txt']}
|
||||
>>> extract_all(['cls.class', '@/path/to/a', '@b.txt'])
|
||||
{'files': [], 'opts': ['cls.class', '@/path/to/a @b.txt']}
|
||||
>>> v = extract_all(['-opt1', 'optval1'] * 1000 + ['/path/to/1.java'])
|
||||
>>> len(v['opts'])
|
||||
2000
|
||||
"""
|
||||
java_files = []
|
||||
java_opts = []
|
||||
# Reversed Javac options parameters
|
||||
rev_opt_params = []
|
||||
java_arg = pop(javac_arguments)
|
||||
while java_arg is not None:
|
||||
if java_arg.endswith('.java'):
|
||||
# Probably got a file
|
||||
remainder, path = extract_filepath(javac_arguments + [java_arg])
|
||||
if path is not None:
|
||||
java_files.append(path)
|
||||
javac_arguments = remainder
|
||||
# The file name can't be in the middle of the option
|
||||
java_opts.extend(extract_argfiles_from_rev(rev_opt_params))
|
||||
rev_opt_params = []
|
||||
else:
|
||||
# A use-case here: *.java dir as an option parameter
|
||||
rev_opt_params.append(java_arg)
|
||||
elif java_arg.startswith('-'):
|
||||
# Got a Javac option
|
||||
option = [java_arg]
|
||||
if len(rev_opt_params) > 0:
|
||||
option.append(' '.join(reversed(rev_opt_params)))
|
||||
rev_opt_params = []
|
||||
java_opts[0:0] = option
|
||||
else:
|
||||
# Got Javac option parameter
|
||||
rev_opt_params.append(java_arg)
|
||||
java_arg = pop(javac_arguments)
|
||||
|
||||
# We may have class names and @argfiles besides java files and options
|
||||
java_opts.extend(extract_argfiles_from_rev(rev_opt_params))
|
||||
|
||||
return {'files': java_files, 'opts': java_opts}
|
||||
|
||||
|
||||
def normalize(path):
|
||||
from inferlib import utils
|
||||
# From Javac docs: If a filename contains embedded spaces,
|
||||
# put the whole filename in double quotes
|
||||
quoted_path = path
|
||||
if ' ' in path:
|
||||
quoted_path = '"' + path + '"'
|
||||
return utils.encode(quoted_path)
|
||||
|
||||
|
||||
class GradleCapture:
|
||||
|
||||
def __init__(self, args, cmd):
|
||||
from inferlib import config, utils
|
||||
|
||||
self.args = args
|
||||
# TODO: make the extraction of targets smarter
|
||||
self.build_cmd = [cmd[0], '--debug'] + cmd[1:]
|
||||
# That contains javac version as well
|
||||
version_str = util.run_cmd_ignore_fail([cmd[0], '--version'])
|
||||
path = os.path.join(self.args.infer_out,
|
||||
config.JAVAC_FILELISTS_FILENAME)
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path)
|
||||
logging.info('Running with:\n' + utils.decode(version_str))
|
||||
|
||||
def get_infer_commands(self, verbose_output):
|
||||
from inferlib import config, jwlib
|
||||
|
||||
argument_start_pattern = ' Compiler arguments: '
|
||||
calls = []
|
||||
seen_build_cmds = set([])
|
||||
for line in verbose_output.split('\n'):
|
||||
if argument_start_pattern in line:
|
||||
content = line.partition(argument_start_pattern)[2].strip()
|
||||
# if we're building both the debug and release configuration
|
||||
# and the build commands are identical up to "release/debug",
|
||||
# only do capture for one set of commands
|
||||
build_agnostic_cmd = content.replace('release', 'debug')
|
||||
if build_agnostic_cmd in seen_build_cmds:
|
||||
continue
|
||||
seen_build_cmds.add(build_agnostic_cmd)
|
||||
arguments = content.split(' ')
|
||||
# Note: do *not* try to filter out empty strings from the arguments (as was done
|
||||
# here previously)! It will make compilation commands like
|
||||
# `javac -classpath '' -Xmaxerrs 1000` fail with "Unrecognized option 1000"
|
||||
extracted = extract_all(arguments)
|
||||
java_files = extracted['files']
|
||||
java_args = extracted['opts']
|
||||
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode='w',
|
||||
suffix='.txt',
|
||||
prefix='gradle_',
|
||||
dir=os.path.join(self.args.infer_out,
|
||||
config.JAVAC_FILELISTS_FILENAME),
|
||||
delete=False) as sources:
|
||||
sources.write('\n'.join(map(normalize, java_files)))
|
||||
sources.flush()
|
||||
java_args.append('@' + sources.name)
|
||||
capture = jwlib.create_infer_command(java_args)
|
||||
calls.append(capture)
|
||||
return calls
|
||||
|
||||
def capture(self):
|
||||
print('Running and capturing gradle compilation...')
|
||||
(build_code, (verbose_out, _)) = util.get_build_output(self.build_cmd)
|
||||
cmds = self.get_infer_commands(verbose_out)
|
||||
capture_code = util.run_compilation_commands(cmds)
|
||||
if build_code != os.EX_OK:
|
||||
return build_code
|
||||
return capture_code
|
@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# 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 os
|
||||
import logging
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
|
||||
def get_build_output(build_cmd):
|
||||
from inferlib import utils
|
||||
# TODO make it return generator to be able to handle large builds
|
||||
proc = subprocess.Popen(build_cmd, stdout=subprocess.PIPE)
|
||||
(out_chars, err_chars) = proc.communicate()
|
||||
out = utils.decode(out_chars) if out_chars is not None else ''
|
||||
err = utils.decode(err_chars) if err_chars is not None else ''
|
||||
if proc.returncode != os.EX_OK:
|
||||
utils.stderr(
|
||||
'ERROR: couldn\'t run compilation command `{}`'.format(build_cmd))
|
||||
logging.error(
|
||||
'ERROR: couldn\'t run compilation command `{}`:\n\
|
||||
*** stdout:\n{}\n*** stderr:\n{}\n'
|
||||
.format(build_cmd, out, err))
|
||||
return (proc.returncode, (out, err))
|
||||
|
||||
|
||||
def run_compilation_commands(cmds):
|
||||
"""runs all the commands passed as argument
|
||||
"""
|
||||
# TODO call it in parallel
|
||||
if cmds is None or len(cmds) == 0:
|
||||
# nothing to capture, the OCaml side will detect that and
|
||||
# display the appropriate warning
|
||||
return os.EX_OK
|
||||
for cmd in cmds:
|
||||
if cmd.start() != os.EX_OK:
|
||||
return os.EX_SOFTWARE
|
||||
return os.EX_OK
|
||||
|
||||
|
||||
def run_cmd_ignore_fail(cmd):
|
||||
try:
|
||||
return subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
except:
|
||||
return 'calling {cmd} failed\n{trace}'.format(
|
||||
cmd=' '.join(cmd),
|
||||
trace=traceback.format_exc())
|
||||
|
||||
|
||||
def log_java_version():
|
||||
java_version = run_cmd_ignore_fail(['java', '-version'])
|
||||
javac_version = run_cmd_ignore_fail(['javac', '-version'])
|
||||
logging.info('java versions:\n%s%s', java_version, javac_version)
|
||||
|
||||
|
||||
def base_argparser(description, module_name):
|
||||
def _func(group_name=module_name):
|
||||
"""This creates an empty argparser for the module, which provides only
|
||||
description/usage information and no arguments."""
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument_group(
|
||||
'{grp} module'.format(grp=group_name),
|
||||
description=description,
|
||||
)
|
||||
return parser
|
||||
return _func
|
@ -1,47 +0,0 @@
|
||||
# Copyright (c) 2009-2013, Monoidics ltd.
|
||||
# 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 os
|
||||
import subprocess
|
||||
from . import config
|
||||
|
||||
|
||||
class InferJavacCapture():
|
||||
|
||||
def __init__(self, javac_args):
|
||||
self.javac_args = javac_args
|
||||
|
||||
def start(self):
|
||||
infer = os.path.join(config.BIN_DIRECTORY, 'infer')
|
||||
# pass --continue to prevent removing the results-dir
|
||||
cmd = [
|
||||
infer,
|
||||
'capture',
|
||||
'--continue',
|
||||
'--', 'javac'
|
||||
] + self.javac_args
|
||||
try:
|
||||
return subprocess.check_call(cmd)
|
||||
except Exception as e:
|
||||
print('Failed to execute:', ' '.join(cmd))
|
||||
raise e
|
||||
|
||||
|
||||
def _get_javac_args(javac_args):
|
||||
# replace any -g:.* flag with -g to preserve debugging symbols
|
||||
args = map(lambda arg: '-g' if '-g:' in arg else arg, javac_args)
|
||||
# skip -Werror
|
||||
args = filter(lambda arg: arg != '-Werror', args)
|
||||
return args
|
||||
|
||||
|
||||
def create_infer_command(javac_args):
|
||||
return InferJavacCapture(_get_javac_args(javac_args))
|
@ -0,0 +1,83 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
open! IStd
|
||||
module L = Logging
|
||||
|
||||
let arg_start_pattern = " Compiler arguments: "
|
||||
|
||||
type javac_data = {files: string list; opts: string list}
|
||||
|
||||
(* file_st / opt_st are 'stacks' where parts of opts or filenames accumulate,
|
||||
these will then later be concatenated and added to files / opts *)
|
||||
type fold_state = {files: string list; opts: string list; opt_st: string list; file_st: string list}
|
||||
|
||||
(* see GradleTest.ml *)
|
||||
let parse_gradle_line ~line =
|
||||
let concat_st lst st = if List.is_empty st then lst else String.concat ~sep:" " st :: lst in
|
||||
let file_exist file = PolyVariantEqual.(Sys.file_exists file = `Yes) in
|
||||
let rev_args = line |> String.strip |> String.split ~on:' ' |> List.rev in
|
||||
let res =
|
||||
List.fold rev_args ~init:{files= []; opts= []; opt_st= []; file_st= []}
|
||||
~f:(fun ({files; opts; opt_st; file_st} as state) arg ->
|
||||
if String.is_suffix arg ~suffix:".java" then
|
||||
if file_exist arg then {state with files= concat_st files (arg :: file_st); file_st= []}
|
||||
else {state with file_st= arg :: file_st}
|
||||
else if String.is_prefix arg ~prefix:"-" then
|
||||
{state with opts= arg :: concat_st opts opt_st; opt_st= []}
|
||||
else if String.is_prefix arg ~prefix:"@" then
|
||||
let fname = String.drop_prefix arg 1 in
|
||||
if file_exist fname then {state with opts= concat_st opts (arg :: opt_st); opt_st= []}
|
||||
else {state with opt_st= arg :: opt_st}
|
||||
else {state with opt_st= arg :: opt_st} )
|
||||
in
|
||||
{files= concat_st res.files res.file_st; opts= res.opts @ res.opt_st}
|
||||
|
||||
|
||||
let normalize path = if String.is_substring path ~substring:" " then "\"" ^ path ^ "\"" else path
|
||||
|
||||
let capture ~prog ~args =
|
||||
let _, java_version =
|
||||
Process.create_process_and_wait_with_output ~prog:"java" ~args:["-version"]
|
||||
in
|
||||
let _, javac_version =
|
||||
Process.create_process_and_wait_with_output ~prog:"javac" ~args:["-version"]
|
||||
in
|
||||
let gradle_version, _ = Process.create_process_and_wait_with_output ~prog ~args:["--version"] in
|
||||
L.environment_info "%s %s %s@." java_version javac_version gradle_version ;
|
||||
let process_gradle_line seen line =
|
||||
match String.substr_index line ~pattern:arg_start_pattern with
|
||||
| Some pos ->
|
||||
let content = String.drop_prefix line (pos + String.length arg_start_pattern) in
|
||||
L.debug Capture Verbose "Processing: %s@." content ;
|
||||
if String.Set.mem seen content then seen
|
||||
else
|
||||
let javac_data = parse_gradle_line ~line:content in
|
||||
let tmpfile, oc =
|
||||
Core.Filename.open_temp_file ~in_dir:Config.temp_file_dir "gradle_files" ""
|
||||
in
|
||||
List.iter javac_data.files ~f:(fun file ->
|
||||
Out_channel.output_string oc (normalize file ^ "\n") ) ;
|
||||
Out_channel.close oc ;
|
||||
Javac.call_infer_javac_capture ~javac_args:(("@" ^ tmpfile) :: javac_data.opts) ;
|
||||
String.Set.add seen content
|
||||
| None ->
|
||||
seen
|
||||
in
|
||||
let gradle_output_file = Filename.temp_file ~in_dir:Config.temp_file_dir "gradle_output" ".log" in
|
||||
let shell_cmd =
|
||||
List.map ~f:Escape.escape_shell (prog :: "--debug" :: args)
|
||||
|> String.concat ~sep:" "
|
||||
|> fun cmd -> Printf.sprintf "%s >'%s'" cmd gradle_output_file
|
||||
in
|
||||
L.progress "[GRADLE] %s@." shell_cmd ;
|
||||
Process.create_process_and_wait ~prog:"sh" ~args:["-c"; shell_cmd] ;
|
||||
match Utils.read_file gradle_output_file with
|
||||
| Ok lines ->
|
||||
let processed = List.fold lines ~init:String.Set.empty ~f:process_gradle_line in
|
||||
L.progress "[GRADLE] processed %d lines" @@ String.Set.length processed
|
||||
| Error _ ->
|
||||
L.die ExternalError "*** failed to read gradle output: %s" gradle_output_file
|
@ -0,0 +1,16 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
type javac_data = {files: string list; opts: string list}
|
||||
|
||||
val parse_gradle_line : line:string -> javac_data
|
||||
(** parse a single gradle output line and extract files and javac opts *)
|
||||
|
||||
val capture : prog:string -> args:string list -> unit
|
||||
(** do a gradle capture with the given prog (i.e. gradle) and args *)
|
@ -0,0 +1,62 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
open OUnit2
|
||||
open Gradle
|
||||
|
||||
let javac_data_eq {files= f1; opts= o1} {files= f2; opts= o2} =
|
||||
let string_list_eq = List.equal String.equal in
|
||||
string_list_eq f1 f2 && string_list_eq o1 o2
|
||||
|
||||
|
||||
let test_parse line files opts =
|
||||
let p = String.concat ~sep:";" in
|
||||
let res = parse_gradle_line ~line in
|
||||
assert_equal ~cmp:javac_data_eq
|
||||
~msg:
|
||||
(Printf.sprintf "f:[%s] <> [%s] || o:[%s] <> [%s]" (p res.files) (p files) (p res.opts)
|
||||
(p opts))
|
||||
res {files; opts}
|
||||
|
||||
|
||||
let tests_wrapper _test_ctxt =
|
||||
let tmpjava = Filename.temp_file "" ".java" in
|
||||
let tmpjavanoexist = "foo" ^/ tmpjava in
|
||||
let tmpnojava = Filename.temp_file "" "" in
|
||||
test_parse "" [] [""] ;
|
||||
test_parse ("-opt1 " ^ tmpjava) [tmpjava] ["-opt1"] ;
|
||||
test_parse ("-opt1 optval1 " ^ tmpjava) [tmpjava] ["-opt1"; "optval1"] ;
|
||||
test_parse
|
||||
("-opt1 optval1 " ^ tmpjava ^ " " ^ tmpjavanoexist)
|
||||
[tmpjava ^ " " ^ tmpjavanoexist]
|
||||
["-opt1"; "optval1"] ;
|
||||
test_parse ("-opt1 opt val1 " ^ tmpjava) [tmpjava] ["-opt1"; "opt val1"] ;
|
||||
test_parse ("-opt1 optval1 " ^ tmpjavanoexist) [tmpjavanoexist] ["-opt1"; "optval1"] ;
|
||||
test_parse "undef1 undef2" [] ["undef1"; "undef2"] ;
|
||||
test_parse
|
||||
("-o " ^ tmpjava ^ " cls.class @" ^ tmpnojava)
|
||||
[tmpjava]
|
||||
["-o"; "cls.class"; "@" ^ tmpnojava] ;
|
||||
test_parse ("-opt1 optval1 " ^ tmpjava ^ " cls.class") [tmpjava] ["-opt1"; "optval1 cls.class"] ;
|
||||
test_parse ("cls.class @" ^ tmpnojava ^ " b.txt") [] ["@" ^ tmpnojava ^ " b.txt"; "cls.class"] ;
|
||||
test_parse ("cls.class @" ^ tmpnojava ^ " @b.txt") [] ["@" ^ tmpnojava ^ " @b.txt"; "cls.class"] ;
|
||||
let rec biglist acc n l = if Int.equal n 0 then acc else biglist (l @ acc) (n - 1) l in
|
||||
let opts = biglist [] 100 ["-opt1"; "optval1"] in
|
||||
test_parse (String.concat ~sep:" " @@ (tmpjava :: opts)) [tmpjava] opts ;
|
||||
test_parse
|
||||
("-d classes/java/main -s java/main " ^ tmpjava)
|
||||
[tmpjava]
|
||||
["-d"; "classes/java/main"; "-s"; "java/main"] ;
|
||||
test_parse "-XDuseUnsharedTable=true -classpath '' -Xmaxerrs 1000" []
|
||||
["-XDuseUnsharedTable=true"; "-classpath"; "''"; "-Xmaxerrs"; "1000"] ;
|
||||
test_parse "-XDuseUnsharedTable=true -classpath foo -Xmaxerrs 1000" []
|
||||
["-XDuseUnsharedTable=true"; "-classpath"; "foo"; "-Xmaxerrs"; "1000"] ;
|
||||
()
|
||||
|
||||
|
||||
let tests = "gradle_integration_suite" >:: tests_wrapper
|
Loading…
Reference in new issue