Home | History | Annotate | Download | only in tools
      1 # this file contains definitions related to the Linux kernel itself
      2 #
      3 
      4 # list here the macros that you know are always defined/undefined when including
      5 # the kernel headers
      6 #
      7 import sys, cpp, re, os.path, string, time
      8 from defaults import *
      9 
     10 verboseSearch = 0
     11 verboseFind   = 0
     12 
     13 ########################################################################
     14 ########################################################################
     15 #####                                                              #####
     16 #####           H E A D E R   S C A N N E R                        #####
     17 #####                                                              #####
     18 ########################################################################
     19 ########################################################################
     20 
     21 
     22 class HeaderScanner:
     23     """a class used to non-recursively detect which Linux kernel headers are
     24        used by a given set of input source files"""
     25 
     26     # to use the HeaderScanner, do the following:
     27     #
     28     #    scanner = HeaderScanner()
     29     #    for path in <your list of files>:
     30     #        scanner.parseFile(path)
     31     #
     32     #    # get the set of Linux headers included by your files
     33     #    headers = scanner.getHeaders()
     34     #
     35     #    # get the set of of input files that do include Linux headers
     36     #    files   = scanner.getFiles()
     37     #
     38     #    note that the result of getHeaders() is a set of strings, each one
     39     #    corresponding to a non-bracketed path name, e.g.:
     40     #
     41     #        set("linux/types","asm/types.h")
     42     #
     43 
     44     # the default algorithm is pretty smart and will analyze the input
     45     # files with a custom C pre-processor in order to optimize out macros,
     46     # get rid of comments, empty lines, etc..
     47     #
     48     # this avoids many annoying false positives... !!
     49     #
     50 
     51     # this regular expression is used to detect include paths that relate to
     52     # the kernel, by default, it selects one of:
     53     #    <linux/*>
     54     #    <asm/*>
     55     #    <asm-generic/*>
     56     #    <mtd/*>
     57     #
     58     re_combined_str=\
     59        r"^.*<((%s)/[\d\w_\+\.\-/]*)>.*$" % string.join(kernel_dirs,"|")
     60 
     61     re_combined = re.compile(re_combined_str)
     62 
     63     # some kernel files choose to include files with relative paths (x86 32/64
     64     # dispatch for instance)
     65     re_rel_dir = re.compile(r'^.*"([\d\w_\+\.\-/]+)".*$')
     66 
     67     def __init__(self,config={}):
     68         """initialize a HeaderScanner"""
     69         self.reset()
     70         self.config = config
     71 
     72     def reset(self,config={}):
     73         self.files    = set()  # set of files being parsed for headers
     74         self.headers  = {}     # maps headers to set of users
     75         self.config   = config
     76 
     77     def checkInclude(self, line, from_file, kernel_root=None):
     78         relative = False
     79         m = HeaderScanner.re_combined.match(line)
     80         if kernel_root and not m:
     81             m = HeaderScanner.re_rel_dir.match(line)
     82             relative = True
     83         if not m: return
     84 
     85         header = m.group(1)
     86         if from_file:
     87             self.files.add(from_file)
     88             if kernel_root and relative:
     89                 hdr_dir = os.path.realpath(os.path.dirname(from_file))
     90                 hdr_dir = hdr_dir.replace("%s/" % os.path.realpath(kernel_root),
     91                                           "")
     92                 if hdr_dir:
     93                     _prefix = "%s/" % hdr_dir
     94                 else:
     95                     _prefix = ""
     96                 header = "%s%s" % (_prefix, header)
     97 
     98         if not header in self.headers:
     99             self.headers[header] = set()
    100 
    101         if from_file:
    102             if verboseFind:
    103                 print "=== %s uses %s" % (from_file, header)
    104             self.headers[header].add(from_file)
    105 
    106     def parseFile(self, path, arch=None, kernel_root=None):
    107         """parse a given file for Linux headers"""
    108         if not os.path.exists(path):
    109             return
    110 
    111         # since tokenizing the file is very slow, we first try a quick grep
    112         # to see if this returns any meaningful results. only if this is true
    113         # do we do the tokenization"""
    114         try:
    115             f = open(path, "rt")
    116         except:
    117             print "!!! can't read '%s'" % path
    118             return
    119 
    120         hasIncludes = False
    121         for line in f:
    122             if (HeaderScanner.re_combined.match(line) or
    123                 (kernel_root and HeaderScanner.re_rel_dir.match(line))):
    124                 hasIncludes = True
    125                 break
    126 
    127         if not hasIncludes:
    128             if verboseSearch: print "::: " + path
    129             return
    130 
    131         if verboseSearch: print "*** " + path
    132 
    133         list = cpp.BlockParser().parseFile(path)
    134         if list:
    135             #list.removePrefixed("CONFIG_",self.config)
    136             macros = kernel_known_macros.copy()
    137             if kernel_root:
    138                 macros.update(self.config)
    139                 if arch and arch in kernel_default_arch_macros:
    140                     macros.update(kernel_default_arch_macros[arch])
    141             list.optimizeMacros(macros)
    142             list.optimizeIf01()
    143             includes = list.findIncludes()
    144             for inc in includes:
    145                 self.checkInclude(inc, path, kernel_root)
    146 
    147     def getHeaders(self):
    148         """return the set of all needed kernel headers"""
    149         return set(self.headers.keys())
    150 
    151     def getHeaderUsers(self,header):
    152         """return the set of all users for a given header"""
    153         return set(self.headers.get(header))
    154 
    155     def getAllUsers(self):
    156         """return a dictionary mapping heaaders to their user set"""
    157         return self.headers.copy()
    158 
    159     def getFiles(self):
    160         """returns the set of files that do include kernel headers"""
    161         return self.files.copy()
    162 
    163 
    164 ##########################################################################
    165 ##########################################################################
    166 #####                                                                #####
    167 #####           H E A D E R   F I N D E R                            #####
    168 #####                                                                #####
    169 ##########################################################################
    170 ##########################################################################
    171 
    172 
    173 class KernelHeaderFinder:
    174     """a class used to scan the kernel headers themselves."""
    175 
    176     # this is different
    177     #  from a HeaderScanner because we need to translate the path returned by
    178     #  HeaderScanner.getHeaders() into possibly architecture-specific ones.
    179     #
    180     # for example, <asm/XXXX.h> needs to be translated in <asm-ARCH/XXXX.h>
    181     # where ARCH is appropriately chosen
    182 
    183     # here's how to use this:
    184     #
    185     #    scanner = HeaderScanner()
    186     #    for path in <your list of user sources>:
    187     #        scanner.parseFile(path)
    188     #
    189     #    used_headers = scanner.getHeaders()
    190     #    finder       = KernelHeaderFinder(used_headers, [ "arm", "x86" ],
    191     #                                      "<kernel_include_path>")
    192     #    all_headers  = finder.scanForAllArchs()
    193     #
    194     #   not that the result of scanForAllArchs() is a list of relative
    195     #   header paths that are not bracketed
    196     #
    197 
    198     def __init__(self,headers,archs,kernel_root,kernel_config):
    199         """init a KernelHeaderScanner,
    200 
    201             'headers' is a list or set of headers,
    202             'archs' is a list of architectures
    203             'kernel_root' is the path to the 'include' directory
    204              of your original kernel sources
    205         """
    206 
    207         if len(kernel_root) > 0 and kernel_root[-1] != "/":
    208             kernel_root += "/"
    209         #print "using kernel_root %s" % kernel_root
    210         self.archs         = archs
    211         self.searched      = set(headers)
    212         self.kernel_root   = kernel_root
    213         self.kernel_config = kernel_config
    214         self.needed        = {}
    215         self.setArch(arch=None)
    216 
    217     def setArch(self,arch=None):
    218         self.curr_arch = arch
    219         self.arch_headers = set()
    220         if arch:
    221             self.prefix = "asm-%s/" % arch
    222         else:
    223             self.prefix = None
    224 
    225     def pathFromHeader(self,header):
    226         path = header
    227         if self.prefix and path.startswith("asm/"):
    228             path = "%s%s" % (self.prefix, path[4:])
    229         return path
    230 
    231     def pathToHeader(self,path):
    232         if self.prefix and path.startswith(self.prefix):
    233             path = "asm/%s" % path[len(self.prefix):]
    234         return "%s" % path
    235 
    236     def setSearchedHeaders(self,headers):
    237         self.searched = set(headers)
    238 
    239     def scanForArch(self):
    240         fparser   = HeaderScanner(config=self.kernel_config)
    241         workqueue = []
    242         needed    = {}
    243         for h in self.searched:
    244             path = self.pathFromHeader(h)
    245             if not path in needed:
    246                 needed[path] = set()
    247             workqueue.append(path)
    248 
    249         i = 0
    250         while i < len(workqueue):
    251             path = workqueue[i]
    252             i   += 1
    253             fparser.parseFile(self.kernel_root + path,
    254                               arch=self.curr_arch, kernel_root=self.kernel_root)
    255             for used in fparser.getHeaders():
    256                 path  = self.pathFromHeader(used)
    257                 if not path in needed:
    258                     needed[path] = set()
    259                     workqueue.append(path)
    260                 for user in fparser.getHeaderUsers(used):
    261                     needed[path].add(user)
    262 
    263         # now copy the arch-specific headers into the global list
    264         for header in needed.keys():
    265             users = needed[header]
    266             if not header in self.needed:
    267                 self.needed[header] = set()
    268 
    269             for user in users:
    270                 self.needed[header].add(user)
    271 
    272     def scanForAllArchs(self):
    273         """scan for all architectures and return the set of all needed kernel headers"""
    274         for arch in self.archs:
    275             self.setArch(arch)
    276             self.scanForArch()
    277 
    278         return set(self.needed.keys())
    279 
    280     def getHeaderUsers(self,header):
    281         """return the set of all users for a given header"""
    282         return set(self.needed[header])
    283 
    284     def getArchHeaders(self,arch):
    285         """return the set of all <asm/...> headers required by a given architecture"""
    286         return set()  # XXX: TODO
    287 
    288 #####################################################################################
    289 #####################################################################################
    290 #####                                                                           #####
    291 #####           C O N F I G   P A R S E R                                       #####
    292 #####                                                                           #####
    293 #####################################################################################
    294 #####################################################################################
    295 
    296 class ConfigParser:
    297     """a class used to parse the Linux kernel .config file"""
    298     re_CONFIG_ = re.compile(r"^(CONFIG_\w+)=(.*)$")
    299 
    300     def __init__(self):
    301         self.items = {}
    302         self.duplicates = False
    303 
    304     def parseLine(self,line):
    305         line = string.strip(line)
    306 
    307         # skip empty and comment lines
    308         if len(line) == 0 or line[0] == "#":
    309             return
    310 
    311         m = ConfigParser.re_CONFIG_.match(line)
    312         if not m: return
    313 
    314         name  = m.group(1)
    315         value = m.group(2)
    316 
    317         if name in self.items:  # aarg, duplicate value
    318             self.duplicates = True
    319 
    320         self.items[name] = value
    321 
    322     def parseFile(self,path):
    323         f = file(path, "r")
    324         for line in f:
    325             if len(line) > 0:
    326                 if line[-1] == "\n":
    327                     line = line[:-1]
    328                     if len(line) > 0 and line[-1] == "\r":
    329                         line = line[:-1]
    330                 self.parseLine(line)
    331         f.close()
    332 
    333     def getDefinitions(self):
    334         """retrieve a dictionary containing definitions for CONFIG_XXX"""
    335         return self.items.copy()
    336 
    337     def __repr__(self):
    338         return repr(self.items)
    339 
    340     def __str__(self):
    341         return str(self.items)
    342