|
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. | |
15 """ | |
16 | |
17 debug = False | |
bradn
2011/09/29 20:25:30
If you're gonna leave this in, maybe put options u
noelallen1
2011/09/30 01:12:45
Done.
| |
18 | |
bradn
2011/09/29 20:25:30
cr
noelallen1
2011/09/30 01:12:45
Done.
| |
19 def DebugPrint(txt): | |
20 if debug: print txt | |
21 | |
bradn
2011/09/29 20:25:30
cr
noelallen1
2011/09/30 01:12:45
Done.
| |
22 class Resolver(object): | |
23 """Resolver finds and generates relative paths for include files. | |
24 | |
25 The Resolver object provides a mechanism to to find and convert a source or | |
26 include filename into a relative path based on provided search paths. | |
27 """ | |
28 def __init__(self, pathobj=os.path): | |
29 self.search_dirs = [] | |
30 self.pathobj = pathobj | |
31 self.cwd = self.pathobj.realpath(os.getcwd()) | |
32 self.offs = len(self.cwd) | |
33 | |
34 def AddOneDirectory(self, pathname): | |
35 """Add an include search path.""" | |
36 pathname = self.pathobj.realpath(pathname) | |
37 DebugPrint('Adding DIR: %s' % pathname) | |
38 if pathname not in self.search_dirs: | |
39 if self.pathobj.isdir(pathname): | |
40 self.search_dirs.append(pathname) | |
41 else: | |
42 sys.stderr.write('Not a directory: %s\n' % pathname) | |
43 return False | |
44 return True | |
45 | |
46 def AddDirectories(self, pathlist): | |
47 """Add list of space separated directories.""" | |
48 failed = False | |
49 dirlist = ' '.join(pathlist) | |
50 for dirname in dirlist.split(' '): | |
51 if not self.AddOneDirectory(dirname): | |
52 failed = True | |
53 return not failed | |
54 | |
55 def GetDirectories(self): | |
56 return self.search_dirs | |
57 | |
58 def RealToRelative(self, filepath, basepath): | |
59 """Returns a relative path from an absolute basepath and filepath.""" | |
60 path_parts = filepath.split(os.sep) | |
61 base_parts = basepath.split(os.sep) | |
62 while path_parts and base_parts and path_parts[0] == base_parts[0]: | |
63 path_parts = path_parts[1:] | |
64 base_parts = base_parts[1:] | |
65 rel_parts = ['..'] * len(base_parts) + path_parts | |
66 return os.sep.join(rel_parts) | |
bradn
2011/09/29 20:25:30
Maybe make this use '/' instead of os.sep, and or
noelallen1
2011/09/30 01:12:45
Done.
| |
67 | |
68 def FilenameToRelative(self, filepath): | |
69 """Returns a relative path from CWD to filepath.""" | |
70 filepath = self.pathobj.realpath(filepath) | |
71 basepath = self.cwd | |
72 return self.RealToRelative(filepath, basepath) | |
73 | |
74 def FindFile(self, filename): | |
75 """Search for <filename> across the search directories, if the path is not | |
76 absolute. Return the filepath relative to the CWD or None. """ | |
77 if self.pathobj.isabs(filename): | |
78 if self.pathobj.exists(filename): | |
79 return self.FilenameToRelative(filename) | |
80 return None | |
81 for pathname in self.search_dirs: | |
82 fullname = os.path.join(pathname, filename) | |
83 if self.pathobj.exists(fullname): | |
84 return self.FilenameToRelative(fullname) | |
85 return None | |
86 | |
87 | |
88 def LoadFile(filename): | |
89 # Catch cases where the file does not exist | |
90 try: | |
91 fd = open(filename) | |
92 except IOError: | |
93 DebugPrint('Exception on file: %s' % filename) | |
94 return '' | |
95 # Go ahead and throw if you fail to read | |
96 return fd.read() | |
97 | |
98 | |
99 class Scanner(object): | |
100 """Scanner searches for '#include' to find dependencies.""" | |
101 | |
102 def __init__(self, loader=None): | |
103 regex = r'\#[ \t]*include[ \t]*[<"]([^>^"]+)[>"]' | |
104 self.parser = re.compile(regex) | |
105 self.loader = loader | |
106 if not loader: | |
107 self.loader = LoadFile | |
108 | |
109 def ScanData(self, data): | |
110 """Generate a list of includes from this text block.""" | |
111 return self.parser.findall(data) | |
112 | |
113 def ScanFile(self, filename): | |
114 """Generate a list of includes from this filename.""" | |
115 includes = self.ScanData(self.loader(filename)) | |
116 DebugPrint('Source %s contains:\n\t%s' % (filename, '\n\t'.join(includes))) | |
117 return includes | |
118 | |
119 | |
120 class WorkQueue(object): | |
121 """WorkQueue contains the list of files to be scanned. | |
122 | |
123 WorkQueue contains provides a queue of files to be processed. The scanner | |
124 will attempt to push new items into the queue, which will be ignored if the | |
125 item is already in the queue. If the item is new, it will be added to the | |
126 work list, which is drained by the scanner. | |
127 """ | |
128 def __init__(self, resolver, scanner=Scanner()): | |
129 self.added_set = set() | |
130 self.todo_list = list() | |
131 self.scanner = scanner | |
132 self.resolver = resolver | |
133 | |
134 def PushIfNew(self, filename): | |
135 """Add this dependency to the list of not already there.""" | |
136 DebugPrint('Adding %s' % filename) | |
137 resolved_name = self.resolver.FindFile(filename) | |
138 if not resolved_name: | |
139 DebugPrint('Failed to resolve %s' % filename) | |
140 return | |
141 DebugPrint('Resolvd as %s' % resolved_name) | |
142 if resolved_name in self.added_set: | |
143 return | |
144 self.todo_list.append(resolved_name) | |
145 self.added_set.add(resolved_name) | |
146 | |
147 def PopIfAvail(self): | |
148 """Fetch the next dependency to search.""" | |
149 if not self.todo_list: | |
150 return None | |
151 return self.todo_list.pop() | |
152 | |
153 def Run(self): | |
154 """Search through the available dependencies until the list becomes empty. | |
155 The list must be primed with one or more source files to search.""" | |
156 scan_name = self.PopIfAvail() | |
157 while scan_name: | |
158 includes = self.scanner.ScanFile(scan_name) | |
159 for include_file in includes: | |
160 self.PushIfNew(include_file) | |
161 scan_name = self.PopIfAvail() | |
162 return sorted(self.added_set) | |
163 | |
164 | |
165 def Main(argv): | |
166 parser = OptionParser() | |
167 parser.add_option('-I', dest='includes', action="append", | |
168 help='Set include path') | |
169 (options, files) = parser.parse_args(argv[1:]) | |
170 | |
171 resolver = Resolver() | |
172 if options.includes: | |
173 if not resolver.AddDirectories(options.includes): | |
174 return -1 | |
175 | |
176 workQ = WorkQueue(resolver) | |
177 for filename in files: | |
178 workQ.PushIfNew(filename) | |
179 | |
180 sorted_list = workQ.Run() | |
181 for pathname in sorted_list: | |
182 sys.stderr.write(pathname + '\n') | |
183 return 0 | |
184 | |
185 | |
186 if __name__ == '__main__': | |
187 sys.exit(Main(sys.argv)) | |
188 | |
OLD | NEW |