1#!/usr/bin/env python3
2
3#------------------------------------------------------------------------------
4# Description of the header clean process
5#------------------------------------------------------------------------------
6# Here is the list of actions performed by this script to clean the original
7# kernel headers.
8#
9# 1. Optimize well-known macros (e.g. __KERNEL__, __KERNEL_STRICT_NAMES)
10#
11#     This pass gets rid of everything that is guarded by a well-known macro
12#     definition. This means that a block like:
13#
14#        #ifdef __KERNEL__
15#        ....
16#        #endif
17#
18#     Will be totally omitted from the output. The optimizer is smart enough to
19#     handle all complex C-preprocessor conditional expression appropriately.
20#     This means that, for example:
21#
22#        #if defined(__KERNEL__) || defined(FOO)
23#        ...
24#        #endif
25#
26#     Will be transformed into:
27#
28#        #ifdef FOO
29#        ...
30#        #endif
31#
32#     See tools/defaults.py for the list of well-known macros used in this pass,
33#     in case you need to update it in the future.
34#
35#     Note that this also removes any reference to a kernel-specific
36#     configuration macro like CONFIG_FOO from the clean headers.
37#
38#
39# 2. Remove variable and function declarations:
40#
41#   This pass scans non-directive text and only keeps things that look like a
42#   typedef/struct/union/enum declaration. This allows us to get rid of any
43#   variables or function declarations that should only be used within the
44#   kernel anyway (and which normally *should* be guarded by an #ifdef
45#   __KERNEL__ ...  #endif block, if the kernel writers were not so messy).
46#
47#   There are, however, a few exceptions: it is seldom useful to keep the
48#   definition of some static inline functions performing very simple
49#   operations. A good example is the optimized 32-bit byte-swap function
50#   found in:
51#
52#     arch-arm/asm/byteorder.h
53#
54#   The list of exceptions is in tools/defaults.py in case you need to update
55#   it in the future.
56#
57#   Note that we do *not* remove macro definitions, including these macro that
58#   perform a call to one of these kernel-header functions, or even define other
59#   functions. We consider it safe since userland applications have no business
60#   using them anyway.
61#
62#
63# 3. Add a standard disclaimer:
64#
65#   The message:
66#
67#   /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
68#
69#   Is prepended to each generated header.
70#------------------------------------------------------------------------------
71
72import sys, cpp, kernel, glob, os, re, getopt, textwrap
73from defaults import *
74from utils import *
75
76def print_error(no_update, msg):
77    if no_update:
78        panic(msg)
79    sys.stderr.write("warning: " + msg)
80
81
82def cleanupFile(dst_file, src_file, rel_path, no_update = True):
83    """reads an original header and perform the cleanup operation on it
84       this functions returns the destination path and the clean header
85       as a single string"""
86    # Check the header path
87    if not os.path.exists(src_file):
88        print_error(no_update, "'%s' does not exist\n" % src_file)
89        return None
90
91    if not os.path.isfile(src_file):
92        print_error(no_update, "'%s' is not a file\n" % src_file)
93        return None
94
95    # Extract the architecture if found.
96    arch = None
97    m = re.search(r"(^|/)asm-([\w\d_\+\.\-]+)/.*", rel_path)
98    if m and m.group(2) != 'generic':
99        arch = m.group(2)
100
101    # Now, let's parse the file.
102    parser = cpp.BlockParser()
103    blocks = parser.parseFile(src_file)
104    if not parser.parsed:
105        print_error(no_update, "Can't parse '%s'" % src_file)
106        return None
107
108    macros = kernel_known_macros.copy()
109    if arch and arch in kernel_default_arch_macros:
110        macros.update(kernel_default_arch_macros[arch])
111
112    blocks.removeStructs(kernel_structs_to_remove)
113    blocks.optimizeMacros(macros)
114    blocks.optimizeIf01()
115    blocks.removeVarsAndFuncs(kernel_known_generic_statics)
116    blocks.replaceTokens(kernel_token_replacements)
117
118    out = StringOutput()
119    out.write(textwrap.dedent("""\
120        /*
121         * This file is auto-generated. Modifications will be lost.
122         *
123         * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/
124         * for more information.
125         */
126        """))
127    blocks.write(out)
128    return out.get()
129
130
131if __name__ == "__main__":
132
133    def usage():
134        print("""\
135    usage:  %s [options] <header_path>
136
137        options:
138            -v    enable verbose mode
139
140            -u    enabled update mode
141                this will try to update the corresponding 'clean header'
142                if the content has changed. with this, you can pass more
143                than one file on the command-line
144
145            -k<path>  specify path of original kernel headers
146            -d<path>  specify path of cleaned kernel headers
147
148        <header_path> must be in a subdirectory of 'original'
149    """ % os.path.basename(sys.argv[0]))
150        sys.exit(1)
151
152    try:
153        optlist, args = getopt.getopt(sys.argv[1:], 'uvk:d:')
154    except:
155        # unrecognized option
156        sys.stderr.write("error: unrecognized option\n")
157        usage()
158
159    no_update = True
160    dst_dir = None
161    src_dir = None
162    for opt, arg in optlist:
163        if opt == '-u':
164            no_update = False
165        elif opt == '-v':
166            logging.basicConfig(level=logging.DEBUG)
167        elif opt == '-k':
168            src_dir = arg
169        elif opt == '-d':
170            dst_dir = arg
171    # get_kernel_dir() and get_kernel_headers_original_dir() require the current
172    # working directory to be a direct or indirect subdirectory of
173    # ANDROID_BUILD_TOP.  Otherwise, these functions print an error message and
174    # exit.  Let's allow the user to run this program from an unrelated
175    # directory, if they specify src_dir and dst_dir on the command line.
176    if dst_dir is None:
177      dst_dir = get_kernel_dir()
178    if src_dir is None:
179      src_dir = get_kernel_headers_original_dir()
180
181    if len(args) == 0:
182        usage()
183
184    if no_update:
185        for path in args:
186            dst_file = os.path.join(dst_dir, path)
187            src_file = os.path.join(src_dir, path)
188            new_data = cleanupFile(dst_file, src_file, path)
189            # Use sys.stdout.write instead of a simple print statement to avoid
190            # sending an extra new line character to stdout.  Running this
191            # program in non-update mode and redirecting stdout to a file should
192            # yield the same result as using update mode, where new_data is
193            # written directly to a file.
194            sys.stdout.write(new_data)
195
196        sys.exit(0)
197
198    # Now let's update our files.
199
200    b = BatchFileUpdater()
201
202    for path in args:
203        dst_file = os.path.join(dst_dir, path)
204        src_file = os.path.join(src_dir, path)
205        new_data = cleanupFile(dst_file, src_file, path, no_update)
206        if not new_data:
207            continue
208
209        b.readFile(dst_file)
210        r = b.editFile(dst_file, new_data)
211        if r == 0:
212            r = "unchanged"
213        elif r == 1:
214            r = "edited"
215        else:
216            r = "added"
217
218        print("cleaning: %-*s -> %-*s (%s)" % (35, path, 35, path, r))
219
220    b.updateFiles()
221
222    sys.exit(0)
223