#!/usr/bin/env python from __future__ import print_function from collections import OrderedDict from shutil import copyfile import argparse import json import os import pprint import re import subprocess import sys import tempfile def normalize(dict_var): for k, v in dict_var.items(): if isinstance(v, OrderedDict): normalize(v) elif isinstance(v, list): for e in v: if isinstance(e, OrderedDict): normalize(e) elif type(v) is unicode: st = v.encode('utf-8') if v != "0x0" and re.match(r"0x[0-9A-Fa-f]+", v): dict_var[k] = u'0x{{.*}}' elif os.path.isfile(v): dict_var[k] = u'{{.*}}' else: splits = (v.split(u' ')) out_splits = [] for split in splits: inner_splits = split.rsplit(u':',2) if os.path.isfile(inner_splits[0]): out_splits.append( u'{{.*}}:%s:%s' %(inner_splits[1], inner_splits[2])) continue out_splits.append(split) dict_var[k] = ' '.join(out_splits) def filter_json(dict_var, filters, out): for k, v in dict_var.items(): if type(v) is unicode: st = v.encode('utf-8') if st in filters: out.append(dict_var) break elif isinstance(v, OrderedDict): filter_json(v, filters, out) elif isinstance(v, list): for e in v: if isinstance(e, OrderedDict): filter_json(e, filters, out) def default_clang_path(): guessed_clang = os.path.join(os.path.dirname(__file__), "clang") if os.path.isfile(guessed_clang): return guessed_clang return None def main(): parser = argparse.ArgumentParser() parser.add_argument("--clang", help="The clang binary (could be a relative or absolute path)", action="store", default=default_clang_path()) parser.add_argument("--source", help="the source file(s). Without --update, the command used to generate the JSON " "will be of the format -cc1 -ast-dump=json ", action="store", nargs=argparse.ONE_OR_MORE, required=True) parser.add_argument("--filters", help="comma separated list of AST filters. Ex: --filters=TypedefDecl,BuiltinType", action="store", default='') update_or_generate_group = parser.add_mutually_exclusive_group() update_or_generate_group.add_argument("--update", help="Update the file in-place", action="store_true") update_or_generate_group.add_argument("--opts", help="other options", action="store", default='', type=str) parser.add_argument("--update-manual", help="When using --update, also update files that do not have the " "autogenerated disclaimer", action="store_true") args = parser.parse_args() if not args.source: sys.exit("Specify the source file to give to clang.") clang_binary = os.path.abspath(args.clang) if not os.path.isfile(clang_binary): sys.exit("clang binary specified not present.") for src in args.source: process_file(src, clang_binary, cmdline_filters=args.filters, cmdline_opts=args.opts, do_update=args.update, force_update=args.update_manual) def process_file(source_file, clang_binary, cmdline_filters, cmdline_opts, do_update, force_update): note_firstline = "// NOTE: CHECK lines have been autogenerated by " \ "gen_ast_dump_json_test.py" filters_line_prefix = "// using --filters=" note = note_firstline cmd = [clang_binary, "-cc1"] if do_update: # When updating the first line of the test must be a RUN: line with open(source_file, "r") as srcf: first_line = srcf.readline() found_autogenerated_line = False filters_line = None for i, line in enumerate(srcf.readlines()): if found_autogenerated_line: # print("Filters line: '", line.rstrip(), "'", sep="") if line.startswith(filters_line_prefix): filters_line = line[len(filters_line_prefix):].rstrip() break if line.startswith(note_firstline): found_autogenerated_line = True # print("Found autogenerated disclaimer at line", i + 1) if not found_autogenerated_line and not force_update: print("Not updating", source_file, "since it is not autogenerated.", file=sys.stderr) return if not cmdline_filters and filters_line: cmdline_filters = filters_line print("Inferred filters as '" + cmdline_filters + "'") if "RUN: %clang_cc1 " not in first_line: sys.exit("When using --update the first line of the input file must contain RUN: %clang_cc1") clang_start = first_line.find("%clang_cc1") + len("%clang_cc1") file_check_idx = first_line.rfind("| FileCheck") if file_check_idx: dump_cmd = first_line[clang_start:file_check_idx] else: dump_cmd = first_line[clang_start:] print("Inferred run arguments as '", dump_cmd, "'", sep="") options = dump_cmd.split() if "-ast-dump=json" not in options: sys.exit("ERROR: RUN: line does not contain -ast-dump=json") if "%s" not in options: sys.exit("ERROR: RUN: line does not contain %s") options.remove("%s") else: options = cmdline_opts.split() options.append("-ast-dump=json") cmd.extend(options) using_ast_dump_filter = any('ast-dump-filter' in arg for arg in cmd) cmd.append(source_file) print("Will run", cmd) filters = set() if cmdline_filters: note += "\n" + filters_line_prefix + cmdline_filters filters = set(cmdline_filters.split(',')) print("Will use the following filters:", filters) try: json_str = subprocess.check_output(cmd) except Exception as ex: print("The clang command failed with %s" % ex) return -1 out_asts = [] if using_ast_dump_filter: splits = re.split('Dumping .*:\n', json_str) if len(splits) > 1: for split in splits[1:]: j = json.loads(split.decode('utf-8'), object_pairs_hook=OrderedDict) normalize(j) out_asts.append(j) else: j = json.loads(json_str.decode('utf-8'), object_pairs_hook=OrderedDict) normalize(j) if len(filters) == 0: out_asts.append(j) else: #assert using_ast_dump_filter is False,\ # "Does not support using compiler's ast-dump-filter "\ # "and the tool's filter option at the same time yet." filter_json(j, filters, out_asts) with tempfile.NamedTemporaryFile("wb", delete=False) as f: with open(source_file, "r") as srcf: for line in srcf.readlines(): # copy up to the note: if line.rstrip() == note_firstline: break f.write(line) f.write(note + "\n") for out_ast in out_asts: append_str = json.dumps(out_ast, indent=1, ensure_ascii=False) out_str = '\n\n' index = 0 for append_line in append_str.splitlines()[2:]: if index == 0: out_str += '// CHECK: %s\n' %(append_line.rstrip()) index += 1 else: out_str += '// CHECK-NEXT: %s\n' %(append_line.rstrip()) f.write(out_str) f.flush() f.close() if do_update: print("Updating json appended source file to %s." % source_file) copyfile(f.name, source_file) else: partition = source_file.rpartition('.') dest_path = '%s-json%s%s' % (partition[0], partition[1], partition[2]) print("Writing json appended source file to %s." % dest_path) copyfile(f.name, dest_path) os.remove(f.name) return 0 if __name__ == '__main__': main()