From a464880611364efca6d95f7959a5942109f00c54 Mon Sep 17 00:00:00 2001 From: Martin Trojer Date: Thu, 19 Mar 2020 03:26:37 -0700 Subject: [PATCH] [gradle] replace python integration with ocaml Reviewed By: ngorogiannis Differential Revision: D20467422 fbshipit-source-id: 2b7cf287d --- Makefile | 2 - Makefile.config | 7 +- infer/lib/python/infer.py | 203 ---------------- infer/lib/python/inferlib/analyze.py | 62 ----- infer/lib/python/inferlib/capture/__init__.py | 4 - infer/lib/python/inferlib/capture/gradle.py | 217 ------------------ infer/lib/python/inferlib/capture/util.py | 76 ------ infer/lib/python/inferlib/jwlib.py | 47 ---- infer/src/base/Config.ml | 3 - infer/src/base/Config.mli | 2 - infer/src/integration/Ant.ml | 19 +- infer/src/integration/Driver.ml | 177 +++++--------- infer/src/integration/Driver.mli | 18 +- infer/src/integration/Gradle.ml | 83 +++++++ infer/src/integration/Gradle.mli | 16 ++ infer/src/integration/Javac.ml | 15 +- infer/src/integration/Javac.mli | 5 + infer/src/unit/GradleTests.ml | 62 +++++ infer/src/unit/inferunit.ml | 1 + 19 files changed, 256 insertions(+), 763 deletions(-) delete mode 100755 infer/lib/python/infer.py delete mode 100644 infer/lib/python/inferlib/analyze.py delete mode 100644 infer/lib/python/inferlib/capture/__init__.py delete mode 100644 infer/lib/python/inferlib/capture/gradle.py delete mode 100644 infer/lib/python/inferlib/capture/util.py delete mode 100644 infer/lib/python/inferlib/jwlib.py create mode 100644 infer/src/integration/Gradle.ml create mode 100644 infer/src/integration/Gradle.mli create mode 100644 infer/src/unit/GradleTests.ml diff --git a/Makefile b/Makefile index 22201788c..0f88ac029 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/Makefile.config b/Makefile.config index 0d95a51e3..7e6c20195 100644 --- a/Makefile.config +++ b/Makefile.config @@ -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) diff --git a/infer/lib/python/infer.py b/infer/lib/python/infer.py deleted file mode 100755 index b5b8f2d9d..000000000 --- a/infer/lib/python/infer.py +++ /dev/null @@ -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='', - dest='nullarg', - default=None, - help=('Command to run the compiler/build-system. ' - 'Supported build commands (run `infer --help -- ` 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 = '' - 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() diff --git a/infer/lib/python/inferlib/analyze.py b/infer/lib/python/inferlib/analyze.py deleted file mode 100644 index 178218e89..000000000 --- a/infer/lib/python/inferlib/analyze.py +++ /dev/null @@ -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='', - 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='', - 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='', 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 .') - -infer_group.add_argument('--java-jar-compiler', - metavar='') diff --git a/infer/lib/python/inferlib/capture/__init__.py b/infer/lib/python/inferlib/capture/__init__.py deleted file mode 100644 index 626423691..000000000 --- a/infer/lib/python/inferlib/capture/__init__.py +++ /dev/null @@ -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. diff --git a/infer/lib/python/inferlib/capture/gradle.py b/infer/lib/python/inferlib/capture/gradle.py deleted file mode 100644 index aceace4d2..000000000 --- a/infer/lib/python/inferlib/capture/gradle.py +++ /dev/null @@ -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 diff --git a/infer/lib/python/inferlib/capture/util.py b/infer/lib/python/inferlib/capture/util.py deleted file mode 100644 index a903e636e..000000000 --- a/infer/lib/python/inferlib/capture/util.py +++ /dev/null @@ -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 diff --git a/infer/lib/python/inferlib/jwlib.py b/infer/lib/python/inferlib/jwlib.py deleted file mode 100644 index 0049251ee..000000000 --- a/infer/lib/python/inferlib/jwlib.py +++ /dev/null @@ -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)) diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 96664dbff..a572e94ae 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -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$" diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index cf6249f83..fd5185cd9 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -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 diff --git a/infer/src/integration/Ant.ml b/infer/src/integration/Ant.ml index 836ea931f..4d1fb31f6 100644 --- a/infer/src/integration/Ant.ml +++ b/infer/src/integration/Ant.ml @@ -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) diff --git a/infer/src/integration/Driver.ml b/infer/src/integration/Driver.ml index 8364d97f9..831a6bf45 100644 --- a/infer/src/integration/Driver.ml +++ b/infer/src/integration/Driver.ml @@ -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 ) diff --git a/infer/src/integration/Driver.mli b/infer/src/integration/Driver.mli index 5f7740f60..1f718f7f7 100644 --- a/infer/src/integration/Driver.mli +++ b/infer/src/integration/Driver.mli @@ -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 diff --git a/infer/src/integration/Gradle.ml b/infer/src/integration/Gradle.ml new file mode 100644 index 000000000..1dbcf5d8d --- /dev/null +++ b/infer/src/integration/Gradle.ml @@ -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 diff --git a/infer/src/integration/Gradle.mli b/infer/src/integration/Gradle.mli new file mode 100644 index 000000000..8925ed78c --- /dev/null +++ b/infer/src/integration/Gradle.mli @@ -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 *) diff --git a/infer/src/integration/Javac.ml b/infer/src/integration/Javac.ml index c20777b4b..b8ea12d7e 100644 --- a/infer/src/integration/Javac.ml +++ b/infer/src/integration/Javac.ml @@ -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 *) diff --git a/infer/src/integration/Javac.mli b/infer/src/integration/Javac.mli index 632861cad..d5a87163a 100644 --- a/infer/src/integration/Javac.mli +++ b/infer/src/integration/Javac.mli @@ -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' *) diff --git a/infer/src/unit/GradleTests.ml b/infer/src/unit/GradleTests.ml new file mode 100644 index 000000000..bc33e41ef --- /dev/null +++ b/infer/src/unit/GradleTests.ml @@ -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 diff --git a/infer/src/unit/inferunit.ml b/infer/src/unit/inferunit.ml index 7f9389194..15de370a8 100644 --- a/infer/src/unit/inferunit.ml +++ b/infer/src/unit/inferunit.ml @@ -34,6 +34,7 @@ let () = ; DifferentialFiltersTests.tests ; DifferentialTests.tests ; FileDiffTests.tests + ; GradleTests.tests ; IListTests.tests ; JavaClassNameTests.tests ; JavaProfilerSamplesTest.tests