OLD | NEW |
| (Empty) |
1 #!/usr/bin/env 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)) | |
OLD | NEW |