text: #!/usr/bin/env python
  #
  # Compare symbols that are exported from the binary against a known snapshot.
  # Return an error if there are new or missing symbols in the library.
  #
  import argparse
  import os
  import subprocess
  import sys
  import re
  
  
  def get_llvm_bin_path():
    if 'LLVM_BIN_PATH' in os.environ:
      return os.environ['LLVM_BIN_PATH']
    return ""
  
  
  def match_symbol(sym_binding, sym_type, sym_section):
    if sym_binding is None or sym_type is None or sym_section is None:
      return False
    if not sym_type.group() == "Function":
      return False
    if not (sym_binding.group() == "Global" or sym_binding.group() == "Weak"):
      return False
    if not sym_section.group() == ".text":
      return False
    return True
  
  
  def parse_readobj_output(output):
    symbol_key = r"Export" if os.name == 'nt' else r"Symbol"
    symbols = re.findall(symbol_key + r" \{[^\}]*\}",
                          output.decode().strip())
    parsed_symbols = []
    for sym in symbols:
      # Name section on Linux has the following structure:
      # SYMBOL_NAME (ID)\n
      # regex takes SYMBOL_NAME before the space.
      # Name section on Windows has the following structure
      # SYMBOL_NAME\n
      # and has special characters in comparison with symbols
      # on Linux
      name = re.search(r"(?<=Name:\s)[^ \n]+", sym)
      if os.name == 'nt':
        # no additional info about the symbols on Windows in comparison with symbols on Linux below
        parsed_symbols.append(name.group())
      else:
        sym_binding = re.search(r"(?<=Binding:\s)[\w]+", sym)
        sym_type = re.search(r"(?<=Type:\s)[\w]+", sym)
        sym_section = re.search(r"(?<=Section:\s)[\.\w]+", sym)
        if match_symbol(sym_binding, sym_type, sym_section):
          parsed_symbols.append(name.group())
    return parsed_symbols
  
  
  def dump_symbols(target_path, output):
    with open(output, "w") as out:
      out.write("################################################################################")
      out.write("\n# This file is automatically generated by abi_check.py tool.")
      out.write("\n# DO NOT EDIT IT MANUALLY. Refer to sycl/doc/developer/ABIPolicyGuide.md for more info.")
      out.write("\n################################################################################")
      out.write("\n\n# RUN: env LLVM_BIN_PATH=%llvm_build_bin_dir python")
      out.write(" %sycl_tools_src_dir/abi_check.py --mode check_symbols")
      if os.name == 'nt':
        out.write(" --reference %s %llvm_build_bin_dir/")
      else:
        out.write(" --reference %s %sycl_libs_dir/")
      out.write(os.path.basename(target_path))
      if os.name == 'nt':
        out.write("\n# REQUIRES: windows")
      else:
        out.write("\n# REQUIRES: linux")
      out.write("\n# UNSUPPORTED: libcxx")
      out.write("\n\n")
      readobj_opts = "--coff-exports" if os.name == 'nt' else "--syms"
      readobj_out = subprocess.check_output([get_llvm_bin_path()+"llvm-readobj",
                                             readobj_opts, target_path])
      symbols = parse_readobj_output(readobj_out)
      symbols.sort()
      out.write("\n".join(symbols))
      out.write("\n")
  
  
  def compare_results(ref_records, records):
    missing_records = set(ref_records).difference(set(records))
    new_records = set(records).difference(set(ref_records))
  
    return (missing_records, new_records)
  
  
  # Dumps symbols from from binary at target_path and compares with a snapshot
  # stored at ref_path. Reports new and absent symbols (if there are any).
  def check_symbols(ref_path, target_path):
    with open(ref_path, "r") as ref:
      ref_symbols = []
      for line in ref:
        if not line.startswith('#') and line.strip():
          ref_symbols.append(line.strip())
  
      readobj_opts = "--coff-exports" if os.name == 'nt' else "--syms"
      readobj_out = subprocess.check_output([get_llvm_bin_path()+"llvm-readobj",
                                             readobj_opts, target_path])
      symbols = parse_readobj_output(readobj_out)
  
      missing_symbols, new_symbols = compare_results(ref_symbols, symbols)
  
      correct_return = True
      if missing_symbols:
        correct_return = False
        print(("There are missing symbols in the new library. It is a breaking "
        "change. Refer to sycl/doc/developer/ABIPolicyGuide.md for further instructions. "
        "Do not forget to update ABI version according to the policy."))
        print('The following symbols are missing from the new object file:\n')
        print("\n".join(missing_symbols))
  
      if new_symbols:
        correct_return = False
        print(("There are new symbols in the new library. It is a non-breaking "
        "change. Refer to sycl/doc/developer/ABIPolicyGuide.md for further instructions."))
        print('The following symbols are new to the object file:\n')
        print("\n".join(new_symbols))
  
      if not correct_return:
        sys.exit(-1)
  
  
  def main():
    parser = argparse.ArgumentParser(description='ABI checker utility.')
    parser.add_argument('--mode', type=str,
                        choices=['check_symbols', 'dump_symbols'],
                        help='ABI checking mode', required=True)
    parser.add_argument('--reference', type=str, help='Reference ABI dump')
    parser.add_argument('--output', type=str, help='Output for dump modes')
    parser.add_argument('target_library', type=str)
  
    args = parser.parse_args()
  
    if args.mode == 'check_symbols':
      if args.reference is None:
        print("Please specify --reference option. Quiting.")
        sys.exit(-2)
      check_symbols(args.reference, args.target_library)
    elif args.mode == 'dump_symbols':
      if args.output is None:
        print("Please specify --output option. Quiting.")
        sys.exit(-2)
      dump_symbols(args.target_library, args.output)
  
  
  if __name__ == "__main__":
    main()
  
  