Adding initial support for Buck for Cxx with flavored targets

Summary: public
Adding initial support for Buck for Cxx on the toplevel infer command.
If a `cxx_binary`/`cxx_library` builds with the command `buck build //targetName:target` then the analysis of Infer can be triggered through the `#infer` flavor.
This change configures buck to run Infer, whenever the `#infer` flavor gets passed: just run
`infer --use-flavors -- buck build //targetName:target#infer`

Please note that when using this feature (`--use-flavors`), the toplevel `infer` command has to be launched from the root of the buck project (which is where the `.buckconfig` file is located) and the `#infer` flavor must be passed on the target.

Reviewed By: jvillard

Differential Revision: D2508296

fb-gh-sync-id: 63c0dea
master
martinoluca 9 years ago committed by facebook-github-bot-7
parent d151977b39
commit 513eee3059

@ -109,11 +109,17 @@ def error(msg):
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
def get_cmd_in_bin_dir(binary_name): def get_infer_bin():
# this relies on the fact that utils.py is located in infer/bin # this relies on the fact that utils.py is located in infer/bin
return os.path.join( return BIN_DIRECTORY
os.path.dirname(os.path.realpath(__file__)),
binary_name)
def get_cmd_in_bin_dir(binary_name):
return os.path.join(get_infer_bin(), binary_name)
def get_infer_root():
return os.path.join(get_infer_bin(), '..', '..')
def write_cmd_streams_to_file(logfile, cmd=None, out=None, err=None): def write_cmd_streams_to_file(logfile, cmd=None, out=None, err=None):
@ -344,6 +350,20 @@ def invoke_function_with_callbacks(
raise raise
def save_as_json(data, filename):
with open(filename, 'w') as file_out:
json.dump(data, file_out, indent=2)
def merge_json_reports(report_paths, merged_report_path):
# TODO: use streams instead of loading the entire json in memory
json_data = []
for json_path in report_paths:
with open(json_path, 'r') as fd:
json_data = json_data + json.loads(fd.read())
save_as_json(json_data, merged_report_path)
def create_json_report(out_dir): def create_json_report(out_dir):
csv_report_filename = os.path.join(out_dir, CSV_REPORT_FILENAME) csv_report_filename = os.path.join(out_dir, CSV_REPORT_FILENAME)
json_report_filename = os.path.join(out_dir, JSON_REPORT_FILENAME) json_report_filename = os.path.join(out_dir, JSON_REPORT_FILENAME)
@ -351,10 +371,9 @@ def create_json_report(out_dir):
with open(csv_report_filename, 'r') as file_in: with open(csv_report_filename, 'r') as file_in:
reader = csv.reader(file_in) reader = csv.reader(file_in)
rows = [row for row in reader] rows = [row for row in reader]
with open(json_report_filename, 'w') as file_out:
headers = rows[0] headers = rows[0]
issues = [dict(zip(headers, row)) for row in rows[1:]] issues = [dict(zip(headers, row)) for row in rows[1:]]
json.dump(issues, file_out, indent=2) save_as_json(issues, json_report_filename)
def get_plural(_str, count): def get_plural(_str, count):

@ -44,19 +44,110 @@ def create_argparser(group_name=MODULE_NAME):
help='Do not use buck distributed cache') help='Do not use buck distributed cache')
group.add_argument('--print-harness', action='store_true', group.add_argument('--print-harness', action='store_true',
help='Print generated harness code (Android only)') help='Print generated harness code (Android only)')
group.add_argument('--use-flavors', action='store_true',
help='Run Infer analysis through the use of flavors. '
'Currently this is supported only for the cxx_* '
'targets of Buck - e.g. cxx_library, cxx_binary - '
'and not for Java. Note: this flag should be used '
'in combination with passing the #infer flavor '
'to the Buck target.')
group.add_argument('--xcode-developer-dir',
help='Specify the path to Xcode developer directory '
'(requires --use-flavors to work)')
return parser return parser
class BuckAnalyzer: class BuckAnalyzer:
def __init__(self, args, cmd): def __init__(self, args, cmd):
self.args = args self.args = args
self.cmd = cmd
util.log_java_version() util.log_java_version()
logging.info(util.run_cmd_ignore_fail(['buck', '--version'])) logging.info(util.run_cmd_ignore_fail(['buck', '--version']))
self.cmd = cmd[2:] # TODO: make the extraction of targets smarter
def capture(self): def capture(self):
try:
if self.args.use_flavors:
return self.capture_with_flavors()
else:
return self.capture_without_flavors()
except subprocess.CalledProcessError as exc:
if self.args.debug:
traceback.print_exc()
return exc.returncode
def create_cxx_buck_configuration_args(self):
# return a string that can be passed in input to buck
# and configures the paths to infer/clang/plugin/xcode
facebook_clang_plugins_root = os.path.join(
utils.get_infer_root(),
'facebook-clang-plugins',
)
clang_path = os.path.join(
facebook_clang_plugins_root,
'clang',
'bin',
'clang',
)
plugin_path = os.path.join(
facebook_clang_plugins_root,
'libtooling',
'build',
'FacebookClangPlugin.dylib',
)
args = [
'--config',
'infer.infer_bin={bin}'.format(bin=utils.get_infer_bin()),
'--config',
'infer.clang_compiler={clang}'.format(clang=clang_path),
'--config',
'infer.clang_plugin={plugin}'.format(plugin=plugin_path),
]
if self.args.xcode_developer_dir is not None:
args.append('--config')
args.append('apple.xcode_developer_dir={devdir}'.format(
devdir=self.args.xcode_developer_dir))
return args
def _get_analysis_result_files(self):
# TODO(8610738): Make targets extraction smarter
buck_results_cmd = [
self.cmd[0],
'targets',
'--show-output'
] + self.cmd[2:] + self.create_cxx_buck_configuration_args()
proc = subprocess.Popen(buck_results_cmd, stdout=subprocess.PIPE)
(buck_output, _) = proc.communicate()
# remove target name prefixes from each line and split them into a list
out = [x.split(None, 1)[1] for x in buck_output.strip().split('\n')]
# from the resulting list, get only what ends in json
return [x for x in out if x.endswith('.json')]
def _run_buck_with_flavors(self):
# TODO: Use buck to identify the project's root folder
if not os.path.isfile('.buckconfig'):
print('Please run this command from the folder where .buckconfig '
'is located')
return os.EX_USAGE
subprocess.check_call(
self.cmd + self.create_cxx_buck_configuration_args())
return os.EX_OK
def capture_with_flavors(self):
ret = self._run_buck_with_flavors()
if not ret == os.EX_OK:
return ret
result_files = self._get_analysis_result_files()
merged_results_path = \
os.path.join(self.args.infer_out, utils.JSON_REPORT_FILENAME)
utils.merge_json_reports(
result_files,
merged_results_path)
# TODO: adapt inferlib.print_errors to support json and print on screen
print('Results saved in {results_path}'.format(
results_path=merged_results_path))
return os.EX_OK
def capture_without_flavors(self):
# BuckAnalyze is a special case, and we run the analysis from here # BuckAnalyze is a special case, and we run the analysis from here
capture_cmd = [utils.get_cmd_in_bin_dir('BuckAnalyze')] capture_cmd = [utils.get_cmd_in_bin_dir('BuckAnalyze')]
if self.args.infer_out is not None: if self.args.infer_out is not None:
@ -71,13 +162,7 @@ class BuckAnalyzer:
capture_cmd.append('--no-cache') capture_cmd.append('--no-cache')
if self.args.print_harness: if self.args.print_harness:
capture_cmd.append('--print-harness') capture_cmd.append('--print-harness')
capture_cmd += self.cmd[2:] # TODO: make extraction of targets smarter
capture_cmd += self.cmd
capture_cmd += ['--analyzer', self.args.analyzer] capture_cmd += ['--analyzer', self.args.analyzer]
try:
subprocess.check_call(capture_cmd) subprocess.check_call(capture_cmd)
return os.EX_OK return os.EX_OK
except subprocess.CalledProcessError as exc:
if self.args.debug:
traceback.print_exc()
return exc.returncode

@ -19,15 +19,9 @@ Analysis examples:
infer -- xcodebuild -target HelloWorldApp -sdk iphonesimulator infer -- xcodebuild -target HelloWorldApp -sdk iphonesimulator
infer -- xcodebuild -workspace HelloWorld.xcworkspace -scheme HelloWorld''' infer -- xcodebuild -workspace HelloWorld.xcworkspace -scheme HelloWorld'''
SCRIPT_DIR = os.path.dirname(__file__) SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
INFER_ROOT = os.path.join(SCRIPT_DIR, '..', '..', '..') CLANG_WRAPPER = os.path.join(SCRIPT_DIR, 'clang')
FCP_ROOT = os.path.join(INFER_ROOT, 'facebook-clang-plugins') CLANGPLUSPLUS_WRAPPER = os.path.join(SCRIPT_DIR, 'clang++')
CLANG_WRAPPER = os.path.join(
SCRIPT_DIR, 'clang',
)
CLANGPLUSPLUS_WRAPPER = os.path.join(
SCRIPT_DIR, 'clang++',
)
def gen_instance(*args): def gen_instance(*args):

Loading…
Cancel
Save