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