[gradle] replace python integration with ocaml

Reviewed By: ngorogiannis

Differential Revision: D20467422

fbshipit-source-id: 2b7cf287d
master
Martin Trojer 5 years ago committed by Facebook GitHub Bot
parent 6504f12029
commit a464880611

@ -688,8 +688,6 @@ ifeq ($(BUILD_JAVA_ANALYZERS),yes)
endif
find infer/lib/python/inferlib/* -type f -print0 | xargs -0 -I \{\} \
$(INSTALL_DATA) -C \{\} '$(DESTDIR)$(libdir)'/infer/\{\}
$(INSTALL_PROGRAM) -C infer/lib/python/infer.py \
'$(DESTDIR)$(libdir)'/infer/infer/lib/python/infer.py
$(INSTALL_PROGRAM) -C infer/lib/python/inferTraceBugs \
'$(DESTDIR)$(libdir)'/infer/infer/lib/python/inferTraceBugs
$(INSTALL_PROGRAM) -C infer/lib/python/report.py \

@ -50,7 +50,6 @@ SPECS_LIB_DIR = $(LIB_DIR)/specs
PYTHON_DIR = $(LIB_DIR)/python
PYTHON_LIB_DIR = $(PYTHON_DIR)/inferlib
CAPTURE_LIB_DIR = $(PYTHON_LIB_DIR)/capture
INFER_BIN = $(BIN_DIR)/infer
INFER_COMMANDS = \
@ -92,8 +91,7 @@ JSR_305_JAR = $(DEPENDENCIES_DIR)/java/jsr-305/jsr305.jar
JAVA_MODELS_JAR = $(LIB_DIR)/java/models.jar
JAVA_DEPS_NO_MODELS = \
$(addprefix $(PYTHON_LIB_DIR)/, analyze.py config.py issues.py jwlib.py source.py utils.py) \
$(addprefix $(CAPTURE_LIB_DIR)/, util.py) \
$(addprefix $(PYTHON_LIB_DIR)/, config.py issues.py source.py utils.py) \
$(ANDROID_JAR) $(GUAVA_JAR) $(JACKSON_JAR) $(JSR_305_JAR) $(INFER_ANNOTATIONS_JAR) \
$(INFER_BIN)
@ -104,8 +102,7 @@ MODELS_RESULTS_FILE = $(SPECS_LIB_DIR)/clang_models
CLANG_DEPS_NO_MODELS = \
$(addprefix $(PYTHON_LIB_DIR)/, \
analyze.py config.py issues.py source.py utils.py) \
$(addprefix $(CAPTURE_LIB_DIR)/, util.py) \
config.py issues.py source.py utils.py) \
$(INFER_BIN)
CLANG_DEPS = $(CLANG_DEPS_NO_MODELS) $(MODELS_RESULTS_FILE)

@ -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))

@ -145,9 +145,6 @@ let global_tenv_filename = ".global.tenv"
(** If true, treat calls to no-arg getters as idempotent w.r.t non-nullness *)
let idempotent_getters = true
(** Our Python script does its own argument parsing and will fail with this error on failure *)
let infer_py_argparse_error_exit_code = 22
let ivar_attributes = "ivar_attributes"
let java_lambda_marker_infix = "$Lambda$"

@ -93,8 +93,6 @@ val global_tenv_filename : string
val idempotent_getters : bool
val infer_py_argparse_error_exit_code : int
val initial_analysis_time : float
val ivar_attributes : string

@ -23,19 +23,6 @@ let extract_javac_args line =
|> Option.join
let do_javac_capture rev_javac_args =
let prog = Config.bin_dir ^/ "infer" in
let args =
"capture" :: "--continue" :: "--" :: "javac"
:: List.rev_filter_map rev_javac_args ~f:(fun arg ->
if String.equal "-Werror" arg then None
else if String.is_substring arg ~substring:"-g:" then Some "-g"
else Some arg )
in
L.debug Capture Verbose "%s %s@." prog (String.concat ~sep:" " args) ;
Process.create_process_and_wait ~prog ~args
type fold_state = {collecting: bool; rev_javac_args: string list}
let capture ~prog ~args =
@ -59,7 +46,8 @@ let capture ~prog ~args =
let collecting = collecting || start_collecting in
let rev_javac_args =
if start_collecting && not (List.is_empty rev_javac_args) then (
do_javac_capture rev_javac_args ; [] )
Javac.call_infer_javac_capture ~javac_args:(List.rev rev_javac_args) ;
[] )
else rev_javac_args
in
let rev_javac_args =
@ -70,4 +58,5 @@ let capture ~prog ~args =
{collecting; rev_javac_args}
else {collecting; rev_javac_args} )
in
if not (List.is_empty res.rev_javac_args) then do_javac_capture res.rev_javac_args
if not (List.is_empty res.rev_javac_args) then
Javac.call_infer_javac_capture ~javac_args:(List.rev res.rev_javac_args)

@ -18,17 +18,17 @@ type mode =
| Analyze
| Ant of {prog: string; args: string list}
| BuckClangFlavor of {build_cmd: string list}
| BuckCompilationDB of BuckMode.clang_compilation_db_deps * string * string list
| BuckGenrule of string
| BuckGenruleMaster of 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
| BuckCompilationDB of {deps: BuckMode.clang_compilation_db_deps; prog: string; args: string list}
| BuckGenrule of {prog: string}
| BuckGenruleMaster of {build_cmd: string list}
| Clang of {compiler: Clang.compiler; prog: string; args: string list}
| ClangCompilationDB of {db_files: [`Escaped of string | `Raw of string] list}
| Gradle of {prog: string; args: string list}
| Javac of {compiler: Javac.compiler; prog: string; args: string list}
| Maven of {prog: string; args: string list}
| NdkBuild of {build_cmd: string list}
| PythonCapture of Config.build_system * string list
| XcodeBuild of {prog: string; args: string list}
| XcodeXcpretty of string * string list
| XcodeXcpretty of {prog: string; args: string list}
let is_analyze_mode = function Analyze -> true | _ -> false
@ -39,40 +39,38 @@ let pp_mode fmt = function
F.fprintf fmt "Ant driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| BuckClangFlavor {build_cmd} ->
F.fprintf fmt "BuckClangFlavor driver mode: build_cmd = %a" Pp.cli_args build_cmd
| BuckGenrule prog ->
F.fprintf fmt "BuckGenRule driver mode:@\nprog = '%s'" prog
| BuckGenruleMaster build_cmd ->
F.fprintf fmt "BuckGenrule driver mode:@\nbuild command = %a" Pp.cli_args build_cmd
| BuckCompilationDB (deps, prog, args) ->
| BuckCompilationDB {deps; prog; args} ->
F.fprintf fmt "BuckCompilationDB driver mode:@\nprog = '%s'@\nargs = %a@\ndeps = %a" prog
Pp.cli_args args BuckMode.pp_clang_compilation_db_deps deps
| BuckGenrule {prog} ->
F.fprintf fmt "BuckGenRule driver mode:@\nprog = '%s'" prog
| BuckGenruleMaster {build_cmd} ->
F.fprintf fmt "BuckGenrule driver mode:@\nbuild command = %a" Pp.cli_args build_cmd
| Clang {prog; args} ->
F.fprintf fmt "Clang driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| ClangCompilationDB _ ->
F.fprintf fmt "ClangCompilationDB driver mode"
| PythonCapture (bs, args) ->
F.fprintf fmt "PythonCapture driver mode:@\nbuild system = '%s'@\nargs = %a"
(Config.string_of_build_system bs)
Pp.cli_args args
| XcodeBuild {prog; args} ->
F.fprintf fmt "XcodeBuild driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| XcodeXcpretty (prog, args) ->
F.fprintf fmt "XcodeXcpretty driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| Javac (_, prog, args) ->
| Gradle {prog; args} ->
F.fprintf fmt "Gradle driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| Javac {prog; args} ->
F.fprintf fmt "Javac driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| Maven (prog, args) ->
| Maven {prog; args} ->
F.fprintf fmt "Maven driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| NdkBuild {build_cmd} ->
F.fprintf fmt "NdkBuild driver mode: build_cmd = %a" Pp.cli_args build_cmd
| Clang (_, prog, args) ->
F.fprintf fmt "Clang driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| XcodeBuild {prog; args} ->
F.fprintf fmt "XcodeBuild driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
| XcodeXcpretty {prog; args} ->
F.fprintf fmt "XcodeXcpretty driver mode:@\nprog = '%s'@\nargs = %a" prog Pp.cli_args args
(* A clean command for each driver mode to be suggested to the user
in case nothing got captured. *)
let clean_compilation_command mode =
match mode with
| BuckCompilationDB (_, prog, _) | Clang (_, prog, _) ->
| BuckCompilationDB {prog} | Clang {prog} ->
Some (prog ^ " clean")
| XcodeXcpretty (prog, args) ->
| XcodeXcpretty {prog; args} ->
Some (String.concat ~sep:" " (List.append (prog :: args) ["clean"]))
| _ ->
None
@ -150,26 +148,6 @@ let reset_duplicates_file () =
create ()
let command_error_handling ~always_die ~prog ~args = function
| Ok _ ->
()
| Error _ as status ->
let log =
if (not always_die) && Config.keep_going then
(* Log error and proceed past the failure when keep going mode is on *)
L.external_error
else L.die InternalError
in
log "%a:@\n %s" Pp.cli_args (prog :: args) (Unix.Exit_or_signal.to_string_hum status)
let run_command ~prog ~args ?(cleanup = command_error_handling ~always_die:false ~prog ~args) () =
Unix.waitpid (Unix.fork_exec ~prog ~argv:(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 () ->
@ -230,42 +208,6 @@ let buck_capture build_cmd =
Buck.clang_flavor_capture ~prog ~buck_build_cmd )
let python_capture build_system build_cmd =
L.progress "Capturing in %s mode...@." (Config.string_of_build_system build_system) ;
let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in
let args =
List.rev_append Config.anon_args
( (if not Config.continue_capture then [] else ["--continue"])
@ ( match Config.force_integration with
| None ->
[]
| Some tool ->
["--force-integration"; Config.string_of_build_system tool] )
@ (match Config.java_jar_compiler with None -> [] | Some p -> ["--java-jar-compiler"; p])
@ (if not Config.debug_mode then [] else ["--debug"])
@ (if Config.filtering then [] else ["--no-filtering"])
@ "-j" :: string_of_int Config.jobs
:: (match Config.load_average with None -> [] | Some l -> ["-l"; string_of_float l])
@ (if not Config.pmd_xml then [] else ["--pmd-xml"])
@ ["--project-root"; Config.project_root]
@ (if not Config.quiet then [] else ["--quiet"])
@ "--out" :: Config.results_dir
::
(match Config.xcode_developer_dir with None -> [] | Some d -> ["--xcode-developer-dir"; d])
@ (if not Config.buck_merge_all_deps then [] else ["--buck-merge-all-deps"])
@ ("--" :: build_cmd) )
in
run_command ~prog:infer_py ~args
~cleanup:(function
| Error (`Exit_non_zero exit_code)
when Int.equal exit_code Config.infer_py_argparse_error_exit_code ->
(* swallow infer.py argument parsing error *)
Config.print_usage_exit ()
| status ->
command_error_handling ~always_die:true ~prog:infer_py ~args status )
()
let capture ~changed_files = function
| Analyze ->
()
@ -274,39 +216,40 @@ let capture ~changed_files = function
Ant.capture ~prog ~args
| BuckClangFlavor {build_cmd} ->
buck_capture build_cmd
| BuckCompilationDB (deps, prog, args) ->
| BuckCompilationDB {deps; prog; args} ->
L.progress "Capturing using Buck's compilation database...@." ;
let json_cdb =
CaptureCompilationDatabase.get_compilation_database_files_buck deps ~prog ~args
in
capture_with_compilation_database ~changed_files json_cdb
| BuckGenrule path ->
| BuckGenrule {prog} ->
L.progress "Capturing for Buck genrule compatibility...@." ;
JMain.from_arguments path
| BuckGenruleMaster build_cmd ->
JMain.from_arguments prog
| BuckGenruleMaster {build_cmd} ->
L.progress "Capturing for BuckGenruleMaster integration...@." ;
BuckGenrule.capture build_cmd
| Clang (compiler, prog, args) ->
| Clang {compiler; prog; args} ->
if CLOpt.is_originator then L.progress "Capturing in make/cc mode...@." ;
Clang.capture compiler ~prog ~args
| ClangCompilationDB db_files ->
| ClangCompilationDB {db_files} ->
L.progress "Capturing using compilation database...@." ;
capture_with_compilation_database ~changed_files db_files
| Javac (compiler, prog, args) ->
| Gradle {prog; args} ->
L.progress "Capturing in gradle mode...@." ;
Gradle.capture ~prog ~args
| Javac {compiler; prog; args} ->
if CLOpt.is_originator then L.progress "Capturing in javac mode...@." ;
Javac.capture compiler ~prog ~args
| Maven (prog, args) ->
| Maven {prog; args} ->
L.progress "Capturing in maven mode...@." ;
Maven.capture ~prog ~args
| NdkBuild {build_cmd} ->
L.progress "Capturing in ndk-build mode...@." ;
NdkBuild.capture ~build_cmd
| PythonCapture (build_system, build_cmd) ->
python_capture build_system build_cmd
| XcodeBuild {prog; args} ->
L.progress "Capturing in xcodebuild mode...@." ;
XcodeBuild.capture ~prog ~args
| XcodeXcpretty (prog, args) ->
| XcodeXcpretty {prog; args} ->
L.progress "Capturing using xcodebuild and xcpretty...@." ;
check_xcpretty () ;
let json_cdb =
@ -512,7 +455,7 @@ let mode_of_build_command build_cmd (buck_mode : BuckMode.t option) =
| [] ->
if not (List.is_empty !Config.clang_compilation_dbs) then (
assert_supported_mode `Clang "clang compilation database" ;
ClangCompilationDB !Config.clang_compilation_dbs )
ClangCompilationDB {db_files= !Config.clang_compilation_dbs} )
else Analyze
| prog :: args -> (
let build_system =
@ -524,39 +467,39 @@ let mode_of_build_command build_cmd (buck_mode : BuckMode.t option) =
in
assert_supported_build_system build_system ;
match ((build_system : Config.build_system), buck_mode) with
| BAnt, _ ->
Ant {prog; args}
| BBuck, None ->
error_no_buck_mode_specified ()
| BBuck, Some (ClangCompilationDB deps) ->
BuckCompilationDB (deps, prog, List.append args (List.rev Config.buck_build_args))
BuckCompilationDB {deps; prog; args= List.append args (List.rev Config.buck_build_args)}
| BBuck, Some ClangFlavors when Config.is_checker_enabled Linters ->
L.user_warning
"WARNING: the linters require --buck-compilation-database to be set.@ Alternatively, \
set --no-linters to disable them and this warning.@." ;
BuckClangFlavor {build_cmd}
| BBuck, Some JavaGenruleMaster ->
BuckGenruleMaster build_cmd
BuckGenruleMaster {build_cmd}
| BBuck, Some ClangFlavors ->
BuckClangFlavor {build_cmd}
| BClang, _ ->
Clang (Clang.Clang, prog, args)
| BMake, _ ->
Clang (Clang.Make, prog, args)
Clang {compiler= Clang.Clang; prog; args}
| BGradle, _ ->
Gradle {prog; args}
| BJava, _ ->
Javac (Javac.Java, prog, args)
Javac {compiler= Javac.Java; prog; args}
| BJavac, _ ->
Javac (Javac.Javac, prog, args)
Javac {compiler= Javac.Javac; prog; args}
| BMake, _ ->
Clang {compiler= Clang.Make; prog; args}
| BMvn, _ ->
Maven (prog, args)
| BXcode, _ when Config.xcpretty ->
XcodeXcpretty (prog, args)
| BXcode, _ ->
XcodeBuild {prog; args}
| BBuck, Some ClangFlavors ->
BuckClangFlavor {build_cmd}
Maven {prog; args}
| BNdk, _ ->
NdkBuild {build_cmd}
| BAnt, _ ->
Ant {prog; args}
| (BGradle as build_system), _ ->
PythonCapture (build_system, build_cmd) )
| BXcode, _ when Config.xcpretty ->
XcodeXcpretty {prog; args}
| BXcode, _ ->
XcodeBuild {prog; args} )
let mode_from_command_line =
@ -571,15 +514,15 @@ let mode_from_command_line =
assert false
(* Sys.argv is never empty *)
in
Clang (Clang.Clang, prog, args)
Clang {compiler= Clang.Clang; prog; args}
| _ when Config.infer_is_javac ->
let build_args =
match Array.to_list (Sys.get_argv ()) with _ :: args -> args | [] -> []
in
Javac (Javac.Javac, "javac", build_args)
Javac {compiler= Javac.Javac; prog= "javac"; args= build_args}
| Some path ->
assert_supported_mode `Java "Buck genrule" ;
BuckGenrule path
BuckGenrule {prog= path}
| None ->
mode_of_build_command (List.rev Config.rest) Config.buck_mode )

@ -15,17 +15,17 @@ type mode =
| Analyze
| Ant of {prog: string; args: string list}
| BuckClangFlavor of {build_cmd: string list}
| BuckCompilationDB of BuckMode.clang_compilation_db_deps * string * string list
| BuckGenrule of string
| BuckGenruleMaster of 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
| BuckCompilationDB of {deps: BuckMode.clang_compilation_db_deps; prog: string; args: string list}
| BuckGenrule of {prog: string}
| BuckGenruleMaster of {build_cmd: string list}
| Clang of {compiler: Clang.compiler; prog: string; args: string list}
| ClangCompilationDB of {db_files: [`Escaped of string | `Raw of string] list}
| Gradle of {prog: string; args: string list}
| Javac of {compiler: Javac.compiler; prog: string; args: string list}
| Maven of {prog: string; args: string list}
| NdkBuild of {build_cmd: string list}
| PythonCapture of Config.build_system * string list
| XcodeBuild of {prog: string; args: string list}
| XcodeXcpretty of string * string list
| XcodeXcpretty of {prog: string; args: string list}
val is_analyze_mode : mode -> bool

@ -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 *)

@ -30,7 +30,7 @@ let compile compiler build_prog build_args =
in
(* Pass non-special args via a file to avoid exceeding the command line size limit. *)
let args_file =
let file = Filename.temp_file ~in_dir:Config.temp_file_dir "args_" "" in
let file = Filename.temp_file ~in_dir:Config.temp_file_dir "javac_args" "" in
let quoted_file_args =
List.map file_args ~f:(fun arg ->
if String.contains arg '\'' then arg else F.sprintf "'%s'" arg )
@ -97,6 +97,19 @@ let no_source_file args =
List.for_all ~f:not_source_file (In_channel.read_lines arg_file) )
let call_infer_javac_capture ~javac_args =
let prog = Config.bin_dir ^/ "infer" in
let args =
"capture" :: "--continue" :: "--" :: "javac"
:: List.filter_map javac_args ~f:(fun arg ->
if String.equal "-Werror" arg then None
else if String.is_substring arg ~substring:"-g:" then Some "-g"
else Some arg )
in
L.debug Capture Verbose "%s %s@." prog (String.concat ~sep:" " args) ;
Process.create_process_and_wait ~prog ~args
let capture compiler ~prog ~args =
match (compiler, Config.capture_blacklist) with
(* Simulates Buck support for compilation commands with no source file *)

@ -9,4 +9,9 @@ open! IStd
type compiler = Java | Javac [@@deriving compare]
val call_infer_javac_capture : javac_args:string list -> unit
(** perform a javac catpure given args to javac, this will shell out to 'infer capture -- javac *)
val capture : compiler -> prog:string -> args:string list -> unit
(** perform capture when given prog and args, this is the entrypoint for infer being called with
'capture -- javac' *)

@ -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

@ -34,6 +34,7 @@ let () =
; DifferentialFiltersTests.tests
; DifferentialTests.tests
; FileDiffTests.tests
; GradleTests.tests
; IListTests.tests
; JavaClassNameTests.tests
; JavaProfilerSamplesTest.tests

Loading…
Cancel
Save