|  | #!/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() |