Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / native_client / build / scan_sources.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 The Native Client 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 import os
7 import re
8 import sys
9
10 """Header Scanner.
11
12 This module will scan a set of input sources for include dependencies.  Use
13 the command-line switch -Ixxxx to add include paths.  All filenames and paths
14 are expected and returned with POSIX separators.
15 """
16
17
18 debug = False
19
20
21 def DebugPrint(txt):
22   if debug: print txt
23
24
25 class PathConverter(object):
26   """PathConverter does path manipulates using Posix style pathnames.
27
28   Regardless of the native path type, all inputs and outputs to the path
29   functions are with POSIX style separators.
30   """
31   def ToNativePath(self, pathname):
32     return os.path.sep.join(pathname.split('/'))
33
34   def ToPosixPath(self, pathname):
35     return '/'.join(pathname.split(os.path.sep))
36
37   def isfile(self, pathname):
38     ospath = self.ToNativePath(pathname)
39     return os.path.isfile(ospath)
40
41   def getcwd(self):
42     return self.ToPosixPath(os.getcwd())
43
44   def isabs(self, pathname):
45     ospath = self.ToNativePath(pathname)
46     return os.path.isabs(ospath)
47
48   def isdir(self, pathname):
49     ospath = self.ToNativePath(pathname)
50     return os.path.isdir(ospath)
51
52   def open(self, pathname):
53     ospath = self.ToNativePath(pathname)
54     return open(ospath)
55
56   def abspath(self, pathname):
57     ospath = self.ToNativePath(pathname)
58     ospath = os.path.abspath(ospath)
59     return self.ToPosixPath(ospath)
60
61   def dirname(self, pathname):
62     ospath = self.ToNativePath(pathname)
63     ospath = os.path.dirname(ospath)
64     return self.ToPosixPath(ospath)
65
66
67 filename_to_relative_cache = {}  # (filepath, basepath) -> relpath
68 findfile_cache = {}  # (tuple(searchdirs), cwd, file) -> filename/None
69 pathisfile_cache = {}  # abspath -> boolean, works because fs is static
70                        # during a run.
71
72
73 class Resolver(object):
74   """Resolver finds and generates relative paths for include files.
75
76   The Resolver object provides a mechanism to to find and convert a source or
77   include filename into a relative path based on provided search paths.  All
78   paths use POSIX style separator.
79   """
80   def __init__(self, pathobj=PathConverter()):
81     self.search_dirs = []
82     self.pathobj = pathobj
83     self.cwd = self.pathobj.getcwd()
84     self.offs = len(self.cwd)
85
86   def AddOneDirectory(self, pathname):
87     """Add an include search path."""
88     pathname = self.pathobj.abspath(pathname)
89     DebugPrint('Adding DIR: %s' % pathname)
90     if pathname not in self.search_dirs:
91       if self.pathobj.isdir(pathname):
92         self.search_dirs.append(pathname)
93       else:
94         # We can end up here when using the gyp generator analyzer. To avoid
95         # spamming only log if debug enabled.
96         DebugPrint('Not a directory: %s\n' % pathname)
97         return False
98     return True
99
100   def RemoveOneDirectory(self, pathname):
101     """Remove an include search path."""
102     pathname = self.pathobj.abspath(pathname)
103     DebugPrint('Removing DIR: %s' % pathname)
104     if pathname in self.search_dirs:
105       self.search_dirs.remove(pathname)
106     return True
107
108   def AddDirectories(self, pathlist):
109     """Add list of space separated directories."""
110     failed = False
111     dirlist = ' '.join(pathlist)
112     for dirname in dirlist.split(' '):
113       if not self.AddOneDirectory(dirname):
114         failed = True
115     return not failed
116
117   def GetDirectories(self):
118     return self.search_dirs
119
120   def RealToRelative(self, filepath, basepath):
121     """Returns a relative path from an absolute basepath and filepath."""
122     cache_key = (filepath, basepath)
123     cache_result = None
124     if cache_key in filename_to_relative_cache:
125       cache_result = filename_to_relative_cache[cache_key]
126       return cache_result
127     def SlowRealToRelative(filepath, basepath):
128       path_parts = filepath.split('/')
129       base_parts = basepath.split('/')
130       while path_parts and base_parts and path_parts[0] == base_parts[0]:
131         path_parts = path_parts[1:]
132         base_parts = base_parts[1:]
133       rel_parts = ['..'] * len(base_parts) + path_parts
134       rel_path = '/'.join(rel_parts)
135       return rel_path
136     rel_path = SlowRealToRelative(filepath, basepath)
137     filename_to_relative_cache[cache_key] = rel_path
138     return rel_path
139
140   def FilenameToRelative(self, filepath):
141     """Returns a relative path from CWD to filepath."""
142     filepath = self.pathobj.abspath(filepath)
143     basepath = self.cwd
144     return self.RealToRelative(filepath, basepath)
145
146   def FindFile(self, filename):
147     """Search for <filename> across the search directories, if the path is not
148        absolute.  Return the filepath relative to the CWD or None. """
149     cache_key = (tuple(self.search_dirs), self.cwd, filename)
150     if cache_key in findfile_cache:
151       cache_result = findfile_cache[cache_key]
152       return cache_result
153     result = None
154     def isfile(absname):
155       res = pathisfile_cache.get(absname)
156       if res is None:
157         res = self.pathobj.isfile(absname)
158         pathisfile_cache[absname] = res
159       return res
160
161     if self.pathobj.isabs(filename):
162       if isfile(filename):
163         result = self.FilenameToRelative(filename)
164     else:
165       for pathname in self.search_dirs:
166         fullname = '%s/%s' % (pathname, filename)
167         if isfile(fullname):
168           result = self.FilenameToRelative(fullname)
169           break
170     findfile_cache[cache_key] = result
171     return result
172
173
174 def LoadFile(filename):
175   # Catch cases where the file does not exist
176   try:
177     fd = PathConverter().open(filename)
178   except IOError:
179     DebugPrint('Exception on file: %s' % filename)
180     return ''
181   # Go ahead and throw if you fail to read
182   return fd.read()
183
184
185 scan_cache = {}  # cache (abs_filename -> include_list)
186
187
188 class Scanner(object):
189   """Scanner searches for '#include' to find dependencies."""
190
191   def __init__(self, loader=None):
192     regex = r'^\s*\#[ \t]*include[ \t]*[<"]([^>"]+)[>"]'
193     self.parser = re.compile(regex, re.M)
194     self.loader = loader
195     if not loader:
196       self.loader = LoadFile
197
198   def ScanData(self, data):
199     """Generate a list of includes from this text block."""
200     return self.parser.findall(data)
201
202   def ScanFile(self, filename):
203     """Generate a list of includes from this filename."""
204     abs_filename = os.path.abspath(filename)
205     if abs_filename in scan_cache:
206       return scan_cache[abs_filename]
207     includes = self.ScanData(self.loader(filename))
208     scan_cache[abs_filename] = includes
209     DebugPrint('Source %s contains:\n\t%s' % (filename, '\n\t'.join(includes)))
210     return includes
211
212
213 class WorkQueue(object):
214   """WorkQueue contains the list of files to be scanned.
215
216   WorkQueue contains provides a queue of files to be processed.  The scanner
217   will attempt to push new items into the queue, which will be ignored if the
218   item is already in the queue.  If the item is new, it will be added to the
219   work list, which is drained by the scanner.
220   """
221   def __init__(self, resolver, scanner=Scanner()):
222     self.added_set = set()
223     self.todo_list = list()
224     self.scanner = scanner
225     self.resolver = resolver
226
227   def PushIfNew(self, filename):
228     """Add this dependency to the list of not already there."""
229     DebugPrint('Adding %s' % filename)
230     resolved_name = self.resolver.FindFile(filename)
231     if not resolved_name:
232       DebugPrint('Failed to resolve %s' % filename)
233       return
234     DebugPrint('Resolvd as %s' % resolved_name)
235     if resolved_name in self.added_set:
236       return
237     self.todo_list.append(resolved_name)
238     self.added_set.add(resolved_name)
239
240   def PopIfAvail(self):
241     """Fetch the next dependency to search."""
242     if not self.todo_list:
243       return None
244     return self.todo_list.pop()
245
246   def Run(self):
247     """Search through the available dependencies until the list becomes empty.
248       The list must be primed with one or more source files to search."""
249     scan_name = self.PopIfAvail()
250     while scan_name:
251       includes = self.scanner.ScanFile(scan_name)
252       # Add the directory of the current scanned file for resolving includes
253       # while processing includes for this file.
254       scan_dir = PathConverter().dirname(scan_name)
255       added_dir = not self.resolver.AddOneDirectory(scan_dir)
256       for include_file in includes:
257         self.PushIfNew(include_file)
258       if added_dir:
259         self.resolver.RemoveOneDirectory(scan_dir)
260       scan_name = self.PopIfAvail()
261     return sorted(self.added_set)
262
263
264 def DoMain(argv):
265   """Entry point used by gyp's pymod_do_main feature."""
266   global debug
267
268   resolver = Resolver()
269   files = []
270
271   arg_type = ''
272   for arg in argv:
273     if arg in ['-I', '-S']:
274       arg_type = arg
275     elif arg == '-D':
276       debug = True
277     elif arg_type == '-I':
278       # Skip generated include directories. These files may not exist and
279       # there should be explicit dependency on the target that generates
280       # these files.
281       if arg.startswith('$!PRODUCT_DIR'):
282         continue
283       resolver.AddDirectories([arg])
284     elif arg_type == '-S':
285       files.append(arg)
286
287   workQ = WorkQueue(resolver)
288   for filename in files:
289     workQ.PushIfNew(filename)
290
291   sorted_list = workQ.Run()
292   return '\n'.join(sorted_list) + '\n'
293
294
295 def Main():
296   result = DoMain(sys.argv[1:])
297   sys.stdout.write(result)
298
299
300 if __name__ == '__main__':
301   Main()