OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 from optparse import OptionParser |
| 7 import os |
| 8 import re |
| 9 import sys |
| 10 |
| 11 """Header Scanner. |
| 12 |
| 13 This module will scan a set of input sources for include dependencies. Use |
| 14 the command-line switch -Ixxxx to add include paths. All filenames and paths |
| 15 are expected and returned with POSIX separators. |
| 16 """ |
| 17 |
| 18 |
| 19 debug = False |
| 20 |
| 21 |
| 22 def DebugPrint(txt): |
| 23 if debug: print txt |
| 24 |
| 25 |
| 26 class PathConverter(object): |
| 27 """PathConverter does path manipulates using Posix style pathnames. |
| 28 |
| 29 Regardless of the native path type, all inputs and outputs to the path |
| 30 functions are with POSIX style separators. |
| 31 """ |
| 32 def ToNativePath(self, pathname): |
| 33 return os.path.sep.join(pathname.split('/')) |
| 34 |
| 35 def ToPosixPath(self, pathname): |
| 36 return '/'.join(pathname.split(os.path.sep)) |
| 37 |
| 38 def exists(self, pathname): |
| 39 ospath = self.ToNativePath(pathname) |
| 40 return os.path.exists(ospath) |
| 41 |
| 42 def getcwd(self): |
| 43 return self.ToPosixPath(os.getcwd()) |
| 44 |
| 45 def isabs(self, pathname): |
| 46 ospath = self.ToNativePath(pathname) |
| 47 return os.path.isabs(ospath) |
| 48 |
| 49 def isdir(self, pathname): |
| 50 ospath = self.ToNativePath(pathname) |
| 51 return os.path.isdir(ospath) |
| 52 |
| 53 def open(self, pathname): |
| 54 ospath = self.ToNativePath(pathname) |
| 55 return open(ospath) |
| 56 |
| 57 def realpath(self, pathname): |
| 58 ospath = self.ToNativePath(pathname) |
| 59 ospath = os.path.realpath(ospath) |
| 60 return self.ToPosixPath(ospath) |
| 61 |
| 62 |
| 63 class Resolver(object): |
| 64 """Resolver finds and generates relative paths for include files. |
| 65 |
| 66 The Resolver object provides a mechanism to to find and convert a source or |
| 67 include filename into a relative path based on provided search paths. All |
| 68 paths use POSIX style separator. |
| 69 """ |
| 70 def __init__(self, pathobj=PathConverter()): |
| 71 self.search_dirs = [] |
| 72 self.pathobj = pathobj |
| 73 self.cwd = self.pathobj.getcwd() |
| 74 self.offs = len(self.cwd) |
| 75 |
| 76 def AddOneDirectory(self, pathname): |
| 77 """Add an include search path.""" |
| 78 pathname = self.pathobj.realpath(pathname) |
| 79 DebugPrint('Adding DIR: %s' % pathname) |
| 80 if pathname not in self.search_dirs: |
| 81 if self.pathobj.isdir(pathname): |
| 82 self.search_dirs.append(pathname) |
| 83 else: |
| 84 sys.stderr.write('Not a directory: %s\n' % pathname) |
| 85 return False |
| 86 return True |
| 87 |
| 88 def AddDirectories(self, pathlist): |
| 89 """Add list of space separated directories.""" |
| 90 failed = False |
| 91 dirlist = ' '.join(pathlist) |
| 92 for dirname in dirlist.split(' '): |
| 93 if not self.AddOneDirectory(dirname): |
| 94 failed = True |
| 95 return not failed |
| 96 |
| 97 def GetDirectories(self): |
| 98 return self.search_dirs |
| 99 |
| 100 def RealToRelative(self, filepath, basepath): |
| 101 """Returns a relative path from an absolute basepath and filepath.""" |
| 102 path_parts = filepath.split('/') |
| 103 base_parts = basepath.split('/') |
| 104 while path_parts and base_parts and path_parts[0] == base_parts[0]: |
| 105 path_parts = path_parts[1:] |
| 106 base_parts = base_parts[1:] |
| 107 rel_parts = ['..'] * len(base_parts) + path_parts |
| 108 return '/'.join(rel_parts) |
| 109 |
| 110 def FilenameToRelative(self, filepath): |
| 111 """Returns a relative path from CWD to filepath.""" |
| 112 filepath = self.pathobj.realpath(filepath) |
| 113 basepath = self.cwd |
| 114 return self.RealToRelative(filepath, basepath) |
| 115 |
| 116 def FindFile(self, filename): |
| 117 """Search for <filename> across the search directories, if the path is not |
| 118 absolute. Return the filepath relative to the CWD or None. """ |
| 119 if self.pathobj.isabs(filename): |
| 120 if self.pathobj.exists(filename): |
| 121 return self.FilenameToRelative(filename) |
| 122 return None |
| 123 for pathname in self.search_dirs: |
| 124 fullname = '%s/%s' % (pathname, filename) |
| 125 if self.pathobj.exists(fullname): |
| 126 return self.FilenameToRelative(fullname) |
| 127 return None |
| 128 |
| 129 |
| 130 def LoadFile(filename): |
| 131 # Catch cases where the file does not exist |
| 132 try: |
| 133 fd = PathConverter().open(filename) |
| 134 except IOError: |
| 135 DebugPrint('Exception on file: %s' % filename) |
| 136 return '' |
| 137 # Go ahead and throw if you fail to read |
| 138 return fd.read() |
| 139 |
| 140 |
| 141 class Scanner(object): |
| 142 """Scanner searches for '#include' to find dependencies.""" |
| 143 |
| 144 def __init__(self, loader=None): |
| 145 regex = r'\#[ \t]*include[ \t]*[<"]([^>^"]+)[>"]' |
| 146 self.parser = re.compile(regex) |
| 147 self.loader = loader |
| 148 if not loader: |
| 149 self.loader = LoadFile |
| 150 |
| 151 def ScanData(self, data): |
| 152 """Generate a list of includes from this text block.""" |
| 153 return self.parser.findall(data) |
| 154 |
| 155 def ScanFile(self, filename): |
| 156 """Generate a list of includes from this filename.""" |
| 157 includes = self.ScanData(self.loader(filename)) |
| 158 DebugPrint('Source %s contains:\n\t%s' % (filename, '\n\t'.join(includes))) |
| 159 return includes |
| 160 |
| 161 |
| 162 class WorkQueue(object): |
| 163 """WorkQueue contains the list of files to be scanned. |
| 164 |
| 165 WorkQueue contains provides a queue of files to be processed. The scanner |
| 166 will attempt to push new items into the queue, which will be ignored if the |
| 167 item is already in the queue. If the item is new, it will be added to the |
| 168 work list, which is drained by the scanner. |
| 169 """ |
| 170 def __init__(self, resolver, scanner=Scanner()): |
| 171 self.added_set = set() |
| 172 self.todo_list = list() |
| 173 self.scanner = scanner |
| 174 self.resolver = resolver |
| 175 |
| 176 def PushIfNew(self, filename): |
| 177 """Add this dependency to the list of not already there.""" |
| 178 DebugPrint('Adding %s' % filename) |
| 179 resolved_name = self.resolver.FindFile(filename) |
| 180 if not resolved_name: |
| 181 DebugPrint('Failed to resolve %s' % filename) |
| 182 return |
| 183 DebugPrint('Resolvd as %s' % resolved_name) |
| 184 if resolved_name in self.added_set: |
| 185 return |
| 186 self.todo_list.append(resolved_name) |
| 187 self.added_set.add(resolved_name) |
| 188 |
| 189 def PopIfAvail(self): |
| 190 """Fetch the next dependency to search.""" |
| 191 if not self.todo_list: |
| 192 return None |
| 193 return self.todo_list.pop() |
| 194 |
| 195 def Run(self): |
| 196 """Search through the available dependencies until the list becomes empty. |
| 197 The list must be primed with one or more source files to search.""" |
| 198 scan_name = self.PopIfAvail() |
| 199 while scan_name: |
| 200 includes = self.scanner.ScanFile(scan_name) |
| 201 for include_file in includes: |
| 202 self.PushIfNew(include_file) |
| 203 scan_name = self.PopIfAvail() |
| 204 return sorted(self.added_set) |
| 205 |
| 206 |
| 207 def Main(argv): |
| 208 global debug |
| 209 parser = OptionParser() |
| 210 parser.add_option('-I', dest='includes', action='append', |
| 211 help='Set include path.') |
| 212 parser.add_option('-D', dest='debug', action='store_true', |
| 213 help='Enable debugging output.', default=False) |
| 214 (options, files) = parser.parse_args(argv[1:]) |
| 215 |
| 216 if options.debug: |
| 217 debug = Trueglobal_var_name, |
| 218 |
| 219 resolver = Resolver() |
| 220 if options.includes: |
| 221 if not resolver.AddDirectories(options.includes): |
| 222 return -1 |
| 223 |
| 224 workQ = WorkQueue(resolver) |
| 225 for filename in files: |
| 226 workQ.PushIfNew(filename) |
| 227 |
| 228 sorted_list = workQ.Run() |
| 229 for pathname in sorted_list: |
| 230 sys.stderr.write(pathname + '\n') |
| 231 return 0 |
| 232 |
| 233 |
| 234 if __name__ == '__main__': |
| 235 sys.exit(Main(sys.argv)) |
| 236 |
OLD | NEW |