1#!/usr/bin/env python
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. Whitespace cleanup:
64#
65#   The final pass removes any comments and empty lines from the final headers.
66#
67#
68# 4. Add a standard disclaimer:
69#
70#   The message:
71#
72#   /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
73#
74#   Is prepended to each generated header.
75#------------------------------------------------------------------------------
76
77import sys, cpp, kernel, glob, os, re, getopt
78from defaults import *
79from utils import *
80
81noUpdate = 1
82
83def cleanupFile(path, original_path):
84    """reads an original header and perform the cleanup operation on it
85       this functions returns the destination path and the clean header
86       as a single string"""
87    # check the header path
88    src_path = path
89
90    if not os.path.exists(src_path):
91        if noUpdate:
92            panic( "file does not exist: '%s'\n" % path )
93        sys.stderr.write( "warning: file does not exit: %s\n" % path )
94        return None, None
95
96    if not os.path.isfile(src_path):
97        if noUpdate:
98            panic( "path is not a file: '%s'\n" % path )
99        sys.stderr.write( "warning: not a file: %s\n" % path )
100        return None, None
101
102    if os.path.commonprefix( [ src_path, original_path ] ) != original_path:
103        if noUpdate:
104            panic( "file is not in 'original' directory: %s\n" % path );
105        sys.stderr.write( "warning: file not in 'original' ignored: %s\n" % path )
106        return None, None
107
108    src_path = src_path[len(original_path):]
109    if len(src_path) > 0 and src_path[0] == '/':
110        src_path = src_path[1:]
111
112    if len(src_path) == 0:
113        panic( "oops, internal error, can't extract correct relative path\n" )
114
115    # convert into destination path, extracting architecture if needed
116    # and the corresponding list of known static functions
117    #
118    arch = None
119    statics = kernel_known_generic_statics
120    m = re.match(r"asm-([\w\d_\+\.\-]+)(/.*)", src_path)
121    if m and m.group(1) != 'generic':
122        dst_path = "arch-%s/asm/%s" % m.groups()
123        arch     = m.group(1)
124        statics  = statics.union( kernel_known_statics.get( arch, set() ) )
125    else:
126        # process headers under the uapi directory
127        # note the "asm" level has been explicitly added in the original
128        # kernel header tree for architectural-dependent uapi headers
129        m_uapi = re.match(r"(uapi)/([\w\d_\+\.\-]+)(/.*)", src_path)
130        if m_uapi:
131            dst_path = src_path
132            m_uapi_arch = re.match(r"asm-([\w\d_\+\.\-]+)", m_uapi.group(2))
133            if m_uapi_arch and m_uapi_arch.group(1) != 'generic':
134                arch     = m_uapi_arch.group(1)
135                statics  = statics.union( kernel_known_statics.get( arch, set() ) )
136        # common headers (ie non-asm and non-uapi)
137        else:
138            dst_path = "common/" + src_path
139
140    dst_path = os.path.normpath( kernel_cleaned_path + "/" + dst_path )
141
142    # now, let's parse the file
143    #
144    blocks = cpp.BlockParser().parseFile(path)
145    if not blocks:
146        sys.stderr.write( "error: can't parse '%s'" % path )
147        sys.exit(1)
148
149    macros = kernel_known_macros.copy()
150    if arch and arch in kernel_default_arch_macros:
151        macros.update(kernel_default_arch_macros[arch])
152
153    if arch and arch in kernel_arch_token_replacements:
154        blocks.replaceTokens( kernel_arch_token_replacements[arch] )
155
156    blocks.optimizeMacros( macros )
157    blocks.optimizeIf01()
158    blocks.removeVarsAndFuncs( statics )
159    blocks.replaceTokens( kernel_token_replacements )
160    blocks.removeComments()
161    blocks.removeMacroDefines( kernel_ignored_macros )
162    blocks.removeWhiteSpace()
163
164    out = StringOutput()
165    out.write( kernel_disclaimer )
166    blocks.writeWithWarning(out, kernel_warning, 4)
167    return dst_path, out.get()
168
169
170if __name__ == "__main__":
171
172    def usage():
173        print """\
174    usage:  %s [options] <header_path>
175
176        options:
177            -v    enable verbose mode
178
179            -u    enabled update mode
180                this will try to update the corresponding 'clean header'
181                if the content has changed. with this, you can pass more
182                than one file on the command-line
183
184            -k<path>  specify path of original kernel headers
185            -d<path>  specify path of cleaned kernel headers
186
187        <header_path> must be in a subdirectory of 'original'
188    """ % os.path.basename(sys.argv[0])
189        sys.exit(1)
190
191    try:
192        optlist, args = getopt.getopt( sys.argv[1:], 'uvk:d:' )
193    except:
194        # unrecognized option
195        sys.stderr.write( "error: unrecognized option\n" )
196        usage()
197
198    for opt, arg in optlist:
199        if opt == '-u':
200            noUpdate = 0
201        elif opt == '-v':
202            verbose = 1
203            D_setlevel(1)
204        elif opt == '-k':
205            kernel_original_path = arg
206        elif opt == '-d':
207            kernel_cleaned_path = arg
208
209    if len(args) == 0:
210        usage()
211
212    if noUpdate:
213        for path in args:
214            dst_path, newdata = cleanupFile(path,kernel_original_path)
215            print newdata
216
217        sys.exit(0)
218
219    # now let's update our files.
220
221    b = BatchFileUpdater()
222
223    for path in args:
224        dst_path, newdata = cleanupFile(path,kernel_original_path)
225        if not dst_path:
226            continue
227
228        b.readFile( dst_path )
229        r = b.editFile( dst_path, newdata )
230        if r == 0:
231            r = "unchanged"
232        elif r == 1:
233            r = "edited"
234        else:
235            r = "added"
236
237        print "cleaning: %-*s -> %-*s (%s)" % ( 35, path, 35, dst_path, r )
238
239
240    b.updateGitFiles()
241
242    sys.exit(0)
243