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\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 tagswhich 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                    contents = fp.read()
81
82                try:
83                    syscall_result = [(mo.groups(), fn)
84                                      for mo in syscall_regex.finditer(contents)]
85                    for tag in struct_tags:
86                        tagged_struct_update(tagged_ret[tag], tag, contents)
87                except Exception:
88                    sys.stderr.write("While parsing %s\n" % fn)
89                    raise
90
91                syscall_ret.extend(syscall_result)
92
93    return syscall_ret, tagged_ret
94
95
96def update_file_if_changed(path, new):
97    if os.path.exists(path):
98        with open(path, 'r') as fp:
99            old = fp.read()
100
101        if new != old:
102            with open(path, 'w') as fp:
103                fp.write(new)
104    else:
105        with open(path, 'w') as fp:
106            fp.write(new)
107
108
109def parse_args():
110    global args
111    parser = argparse.ArgumentParser(
112        description=__doc__,
113        formatter_class=argparse.RawDescriptionHelpFormatter)
114
115    parser.add_argument("-i", "--include", required=True, action='append',
116                        help='''include directories recursively scanned
117                        for .h files.  Can be specified multiple times:
118                        -i topdir1 -i topdir2 ...''')
119    parser.add_argument(
120        "-j", "--json-file", required=True,
121        help="Write system call prototype information as json to file")
122    parser.add_argument(
123        "-t", "--tag-struct-file", required=True,
124        help="Write tagged struct name information as json to file")
125
126    args = parser.parse_args()
127
128
129def main():
130    parse_args()
131
132    syscalls, tagged = analyze_headers(args.include)
133
134    # Only write json files if they don't exist or have changes since
135    # they will force an incremental rebuild.
136
137    syscalls_in_json = json.dumps(
138        syscalls,
139        indent=4,
140        sort_keys=True
141    )
142    update_file_if_changed(args.json_file, syscalls_in_json)
143
144    tagged_struct_in_json = json.dumps(
145        tagged,
146        indent=4,
147        sort_keys=True
148    )
149    update_file_if_changed(args.tag_struct_file, tagged_struct_in_json)
150
151
152if __name__ == "__main__":
153    main()
154