text: #!/usr/bin/env python3
  #
  # Copyright (c) 2017 Intel Corporation
  #
  # SPDX-License-Identifier: Apache-2.0
  
  """
  Script to scan Zephyr include directories and emit system call and subsystem metadata
  
  System calls require a great deal of boilerplate code in order to implement
  completely. This script is the first step in the build system's process of
  auto-generating this code by doing a text scan of directories containing
  C or header files, and building up a database of system calls and their
  function call prototypes. This information is emitted to a generated
  JSON file for further processing.
  
  This script also scans for struct definitions such as __subsystem and
  __net_socket, emitting a JSON dictionary mapping tags to all the struct
  declarations found that were tagged with them.
  
  If the output JSON file already exists, its contents are checked against
  what information this script would have outputted; if the result is that the
  file would be unchanged, it is not modified to prevent unnecessary
  incremental builds.
  """
  
  import sys
  import re
  import argparse
  import os
  import json
  
  regex_flags = re.MULTILINE | re.VERBOSE
  
  syscall_regex = re.compile(r'''
  __syscall\s+                    # __syscall attribute, must be first
  ([^(]+)                         # type and name of system call (split later)
  [(]                             # Function opening parenthesis
  ([^)]*)                         # Arg list (split later)
  [)]                             # Closing parenthesis
  ''', regex_flags)
  
  struct_tags = ["__subsystem", "__net_socket"]
  
  tagged_struct_decl_template = r'''
  %s\s+                           # tag, must be first
  struct\s+                       # struct keyword is next
  ([^{]+)                         # name of subsystem
  [{]                             # Open curly bracket
  '''
  
  def tagged_struct_update(target_list, tag, contents):
      regex = re.compile(tagged_struct_decl_template % tag, regex_flags)
      items = [mo.groups()[0].strip() for mo in regex.finditer(contents)]
      target_list.extend(items)
  
  
  def analyze_headers(multiple_directories):
      syscall_ret = []
      tagged_ret = {}
  
      for tag in struct_tags:
          tagged_ret[tag] = []
  
      for base_path in multiple_directories:
          for root, dirs, files in os.walk(base_path, topdown=True):
              dirs.sort()
              files.sort()
              for fn in files:
  
                  # toolchain/common.h has the definitions of these tags which we
                  # don't want to trip over
                  path = os.path.join(root, fn)
                  if (not (path.endswith(".h") or path.endswith(".c")) or
                          path.endswith(os.path.join(os.sep, 'toolchain',
                                                     'common.h'))):
                      continue
  
                  with open(path, "r", encoding="utf-8") as fp:
                      contents = fp.read()
  
                  try:
                      syscall_result = [(mo.groups(), fn)
                                        for mo in syscall_regex.finditer(contents)]
                      for tag in struct_tags:
                          tagged_struct_update(tagged_ret[tag], tag, contents)
                  except Exception:
                      sys.stderr.write("While parsing %s\n" % fn)
                      raise
  
                  syscall_ret.extend(syscall_result)
  
      return syscall_ret, tagged_ret
  
  
  def update_file_if_changed(path, new):
      if os.path.exists(path):
          with open(path, 'r') as fp:
              old = fp.read()
  
          if new != old:
              with open(path, 'w') as fp:
                  fp.write(new)
      else:
          with open(path, 'w') as fp:
              fp.write(new)
  
  
  def parse_args():
      global args
      parser = argparse.ArgumentParser(
          description=__doc__,
          formatter_class=argparse.RawDescriptionHelpFormatter)
  
      parser.add_argument("-i", "--include", required=True, action='append',
                          help='''include directories recursively scanned
                          for .h files.  Can be specified multiple times:
                          -i topdir1 -i topdir2 ...''')
      parser.add_argument(
          "-j", "--json-file", required=True,
          help="Write system call prototype information as json to file")
      parser.add_argument(
          "-t", "--tag-struct-file", required=True,
          help="Write tagged struct name information as json to file")
  
      args = parser.parse_args()
  
  
  def main():
      parse_args()
  
      syscalls, tagged = analyze_headers(args.include)
  
      # Only write json files if they don't exist or have changes since
      # they will force an incremental rebuild.
  
      syscalls_in_json = json.dumps(
          syscalls,
          indent=4,
          sort_keys=True
      )
      update_file_if_changed(args.json_file, syscalls_in_json)
  
      tagged_struct_in_json = json.dumps(
          tagged,
          indent=4,
          sort_keys=True
      )
      update_file_if_changed(args.tag_struct_file, tagged_struct_in_json)
  
  
  if __name__ == "__main__":
      main()
  