237 строки
7.0 KiB
Python
Executable File
237 строки
7.0 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
from optparse import OptionParser
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
"""Header Scanner.
|
|
|
|
This module will scan a set of input sources for include dependencies. Use
|
|
the command-line switch -Ixxxx to add include paths. All filenames and paths
|
|
are expected and returned with POSIX separators.
|
|
"""
|
|
|
|
|
|
debug = False
|
|
|
|
|
|
def DebugPrint(txt):
|
|
if debug: print txt
|
|
|
|
|
|
class PathConverter(object):
|
|
"""PathConverter does path manipulates using Posix style pathnames.
|
|
|
|
Regardless of the native path type, all inputs and outputs to the path
|
|
functions are with POSIX style separators.
|
|
"""
|
|
def ToNativePath(self, pathname):
|
|
return os.path.sep.join(pathname.split('/'))
|
|
|
|
def ToPosixPath(self, pathname):
|
|
return '/'.join(pathname.split(os.path.sep))
|
|
|
|
def exists(self, pathname):
|
|
ospath = self.ToNativePath(pathname)
|
|
return os.path.exists(ospath)
|
|
|
|
def getcwd(self):
|
|
return self.ToPosixPath(os.getcwd())
|
|
|
|
def isabs(self, pathname):
|
|
ospath = self.ToNativePath(pathname)
|
|
return os.path.isabs(ospath)
|
|
|
|
def isdir(self, pathname):
|
|
ospath = self.ToNativePath(pathname)
|
|
return os.path.isdir(ospath)
|
|
|
|
def open(self, pathname):
|
|
ospath = self.ToNativePath(pathname)
|
|
return open(ospath)
|
|
|
|
def realpath(self, pathname):
|
|
ospath = self.ToNativePath(pathname)
|
|
ospath = os.path.realpath(ospath)
|
|
return self.ToPosixPath(ospath)
|
|
|
|
|
|
class Resolver(object):
|
|
"""Resolver finds and generates relative paths for include files.
|
|
|
|
The Resolver object provides a mechanism to to find and convert a source or
|
|
include filename into a relative path based on provided search paths. All
|
|
paths use POSIX style separator.
|
|
"""
|
|
def __init__(self, pathobj=PathConverter()):
|
|
self.search_dirs = []
|
|
self.pathobj = pathobj
|
|
self.cwd = self.pathobj.getcwd()
|
|
self.offs = len(self.cwd)
|
|
|
|
def AddOneDirectory(self, pathname):
|
|
"""Add an include search path."""
|
|
pathname = self.pathobj.realpath(pathname)
|
|
DebugPrint('Adding DIR: %s' % pathname)
|
|
if pathname not in self.search_dirs:
|
|
if self.pathobj.isdir(pathname):
|
|
self.search_dirs.append(pathname)
|
|
else:
|
|
sys.stderr.write('Not a directory: %s\n' % pathname)
|
|
return False
|
|
return True
|
|
|
|
def AddDirectories(self, pathlist):
|
|
"""Add list of space separated directories."""
|
|
failed = False
|
|
dirlist = ' '.join(pathlist)
|
|
for dirname in dirlist.split(' '):
|
|
if not self.AddOneDirectory(dirname):
|
|
failed = True
|
|
return not failed
|
|
|
|
def GetDirectories(self):
|
|
return self.search_dirs
|
|
|
|
def RealToRelative(self, filepath, basepath):
|
|
"""Returns a relative path from an absolute basepath and filepath."""
|
|
path_parts = filepath.split('/')
|
|
base_parts = basepath.split('/')
|
|
while path_parts and base_parts and path_parts[0] == base_parts[0]:
|
|
path_parts = path_parts[1:]
|
|
base_parts = base_parts[1:]
|
|
rel_parts = ['..'] * len(base_parts) + path_parts
|
|
return '/'.join(rel_parts)
|
|
|
|
def FilenameToRelative(self, filepath):
|
|
"""Returns a relative path from CWD to filepath."""
|
|
filepath = self.pathobj.realpath(filepath)
|
|
basepath = self.cwd
|
|
return self.RealToRelative(filepath, basepath)
|
|
|
|
def FindFile(self, filename):
|
|
"""Search for <filename> across the search directories, if the path is not
|
|
absolute. Return the filepath relative to the CWD or None. """
|
|
if self.pathobj.isabs(filename):
|
|
if self.pathobj.exists(filename):
|
|
return self.FilenameToRelative(filename)
|
|
return None
|
|
for pathname in self.search_dirs:
|
|
fullname = '%s/%s' % (pathname, filename)
|
|
if self.pathobj.exists(fullname):
|
|
return self.FilenameToRelative(fullname)
|
|
return None
|
|
|
|
|
|
def LoadFile(filename):
|
|
# Catch cases where the file does not exist
|
|
try:
|
|
fd = PathConverter().open(filename)
|
|
except IOError:
|
|
DebugPrint('Exception on file: %s' % filename)
|
|
return ''
|
|
# Go ahead and throw if you fail to read
|
|
return fd.read()
|
|
|
|
|
|
class Scanner(object):
|
|
"""Scanner searches for '#include' to find dependencies."""
|
|
|
|
def __init__(self, loader=None):
|
|
regex = r'\#[ \t]*include[ \t]*[<"]([^>^"]+)[>"]'
|
|
self.parser = re.compile(regex)
|
|
self.loader = loader
|
|
if not loader:
|
|
self.loader = LoadFile
|
|
|
|
def ScanData(self, data):
|
|
"""Generate a list of includes from this text block."""
|
|
return self.parser.findall(data)
|
|
|
|
def ScanFile(self, filename):
|
|
"""Generate a list of includes from this filename."""
|
|
includes = self.ScanData(self.loader(filename))
|
|
DebugPrint('Source %s contains:\n\t%s' % (filename, '\n\t'.join(includes)))
|
|
return includes
|
|
|
|
|
|
class WorkQueue(object):
|
|
"""WorkQueue contains the list of files to be scanned.
|
|
|
|
WorkQueue contains provides a queue of files to be processed. The scanner
|
|
will attempt to push new items into the queue, which will be ignored if the
|
|
item is already in the queue. If the item is new, it will be added to the
|
|
work list, which is drained by the scanner.
|
|
"""
|
|
def __init__(self, resolver, scanner=Scanner()):
|
|
self.added_set = set()
|
|
self.todo_list = list()
|
|
self.scanner = scanner
|
|
self.resolver = resolver
|
|
|
|
def PushIfNew(self, filename):
|
|
"""Add this dependency to the list of not already there."""
|
|
DebugPrint('Adding %s' % filename)
|
|
resolved_name = self.resolver.FindFile(filename)
|
|
if not resolved_name:
|
|
DebugPrint('Failed to resolve %s' % filename)
|
|
return
|
|
DebugPrint('Resolvd as %s' % resolved_name)
|
|
if resolved_name in self.added_set:
|
|
return
|
|
self.todo_list.append(resolved_name)
|
|
self.added_set.add(resolved_name)
|
|
|
|
def PopIfAvail(self):
|
|
"""Fetch the next dependency to search."""
|
|
if not self.todo_list:
|
|
return None
|
|
return self.todo_list.pop()
|
|
|
|
def Run(self):
|
|
"""Search through the available dependencies until the list becomes empty.
|
|
The list must be primed with one or more source files to search."""
|
|
scan_name = self.PopIfAvail()
|
|
while scan_name:
|
|
includes = self.scanner.ScanFile(scan_name)
|
|
for include_file in includes:
|
|
self.PushIfNew(include_file)
|
|
scan_name = self.PopIfAvail()
|
|
return sorted(self.added_set)
|
|
|
|
|
|
def Main(argv):
|
|
global debug
|
|
parser = OptionParser()
|
|
parser.add_option('-I', dest='includes', action='append',
|
|
help='Set include path.')
|
|
parser.add_option('-D', dest='debug', action='store_true',
|
|
help='Enable debugging output.', default=False)
|
|
(options, files) = parser.parse_args(argv[1:])
|
|
|
|
if options.debug:
|
|
debug = Trueglobal_var_name,
|
|
|
|
resolver = Resolver()
|
|
if options.includes:
|
|
if not resolver.AddDirectories(options.includes):
|
|
return -1
|
|
|
|
workQ = WorkQueue(resolver)
|
|
for filename in files:
|
|
workQ.PushIfNew(filename)
|
|
|
|
sorted_list = workQ.Run()
|
|
for pathname in sorted_list:
|
|
sys.stderr.write(pathname + '\n')
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(Main(sys.argv))
|
|
|