1#!/usr/bin/env python3 2# 3# Copyright (c) 2017 Intel Corporation 4# 5# SPDX-License-Identifier: Apache-2.0 6 7""" 8Script to scan Zephyr include directories and emit system call and subsystem metadata 9 10System calls require a great deal of boilerplate code in order to implement 11completely. This script is the first step in the build system's process of 12auto-generating this code by doing a text scan of directories containing 13C or header files, and building up a database of system calls and their 14function call prototypes. This information is emitted to a generated 15JSON file for further processing. 16 17This script also scans for struct definitions such as __subsystem and 18__net_socket, emitting a JSON dictionary mapping tags to all the struct 19declarations found that were tagged with them. 20 21If the output JSON file already exists, its contents are checked against 22what information this script would have outputted; if the result is that the 23file would be unchanged, it is not modified to prevent unnecessary 24incremental builds. 25""" 26 27import sys 28import re 29import argparse 30import os 31import json 32 33regex_flags = re.MULTILINE | re.VERBOSE 34 35syscall_regex = re.compile(r''' 36(?:__syscall|__syscall_always_inline)\s+ # __syscall attribute, must be first 37([^(]+) # type and name of system call (split later) 38[(] # Function opening parenthesis 39([^)]*) # Arg list (split later) 40[)] # Closing parenthesis 41''', regex_flags) 42 43struct_tags = ["__subsystem", "__net_socket"] 44 45tagged_struct_decl_template = r''' 46%s\s+ # tag, must be first 47struct\s+ # struct keyword is next 48([^{]+) # name of subsystem 49[{] # Open curly bracket 50''' 51 52def tagged_struct_update(target_list, tag, contents): 53 regex = re.compile(tagged_struct_decl_template % tag, regex_flags) 54 items = [mo.groups()[0].strip() for mo in regex.finditer(contents)] 55 target_list.extend(items) 56 57 58def analyze_headers(multiple_directories): 59 syscall_ret = [] 60 tagged_ret = {} 61 62 for tag in struct_tags: 63 tagged_ret[tag] = [] 64 65 for base_path in multiple_directories: 66 for root, dirs, files in os.walk(base_path, topdown=True): 67 dirs.sort() 68 files.sort() 69 for fn in files: 70 71 # toolchain/common.h has the definitions of these tags which we 72 # don't want to trip over 73 path = os.path.join(root, fn) 74 if (not (path.endswith(".h") or path.endswith(".c")) or 75 path.endswith(os.path.join(os.sep, 'toolchain', 76 'common.h'))): 77 continue 78 79 with open(path, "r", encoding="utf-8") as fp: 80 try: 81 contents = fp.read() 82 except Exception: 83 sys.stderr.write("Error decoding %s\n" % path) 84 raise 85 86 try: 87 syscall_result = [(mo.groups(), fn) 88 for mo in syscall_regex.finditer(contents)] 89 for tag in struct_tags: 90 tagged_struct_update(tagged_ret[tag], tag, contents) 91 except Exception: 92 sys.stderr.write("While parsing %s\n" % fn) 93 raise 94 95 syscall_ret.extend(syscall_result) 96 97 return syscall_ret, tagged_ret 98 99 100def update_file_if_changed(path, new): 101 if os.path.exists(path): 102 with open(path, 'r') as fp: 103 old = fp.read() 104 105 if new != old: 106 with open(path, 'w') as fp: 107 fp.write(new) 108 else: 109 with open(path, 'w') as fp: 110 fp.write(new) 111 112 113def parse_args(): 114 global args 115 parser = argparse.ArgumentParser( 116 description=__doc__, 117 formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) 118 119 parser.add_argument("-i", "--include", required=True, action='append', 120 help='''include directories recursively scanned 121 for .h files. Can be specified multiple times: 122 -i topdir1 -i topdir2 ...''') 123 parser.add_argument( 124 "-j", "--json-file", required=True, 125 help="Write system call prototype information as json to file") 126 parser.add_argument( 127 "-t", "--tag-struct-file", required=True, 128 help="Write tagged struct name information as json to file") 129 130 args = parser.parse_args() 131 132 133def main(): 134 parse_args() 135 136 syscalls, tagged = analyze_headers(args.include) 137 138 # Only write json files if they don't exist or have changes since 139 # they will force an incremental rebuild. 140 141 syscalls_in_json = json.dumps( 142 syscalls, 143 indent=4, 144 sort_keys=True 145 ) 146 update_file_if_changed(args.json_file, syscalls_in_json) 147 148 tagged_struct_in_json = json.dumps( 149 tagged, 150 indent=4, 151 sort_keys=True 152 ) 153 update_file_if_changed(args.tag_struct_file, tagged_struct_in_json) 154 155 156if __name__ == "__main__": 157 main() 158