X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=src%2Fengine%2FSCons%2FScanner%2F__init__.py;h=3cfe4b7fe0ed2779a3d363b441a51b6170f868f8;hb=704f6e2480ef60718f1aa42c266f04afc9c79580;hp=8432638e41dd4bb3d36d0374e4c2b9164346c156;hpb=d87dac2e6d4a89a587c2764912a8293a75e5a285;p=scons.git diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py index 8432638e..3cfe4b7f 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -32,7 +32,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import re import SCons.Node.FS -import SCons.Sig import SCons.Util @@ -44,30 +43,41 @@ class _Null: _null = _Null def Scanner(function, *args, **kw): - """Public interface factory function for creating different types + """ + Public interface factory function for creating different types of Scanners based on the different types of "functions" that may - be supplied.""" + be supplied. + + TODO: Deprecate this some day. We've moved the functionality + inside the Base class and really don't need this factory function + any more. It was, however, used by some of our Tool modules, so + the call probably ended up in various people's custom modules + patterned on SCons code. + """ if SCons.Util.is_Dict(function): - return apply(Selector, (function,) + args, kw) + return Selector(function, *args, **kw) else: - return apply(Base, (function,) + args, kw) + return Base(function, *args, **kw) + + class FindPathDirs: - """A class to bind a specific *PATH variable name and the fs object - to a function that will return all of the *path directories.""" - def __init__(self, variable, fs): + """A class to bind a specific *PATH variable name to a function that + will return all of the *path directories.""" + def __init__(self, variable): self.variable = variable - self.fs = fs - def __call__(self, env, dir, argument=None): + def __call__(self, env, dir=None, target=None, source=None, argument=None): + import SCons.PathList try: path = env[self.variable] except KeyError: return () - return tuple(self.fs.Rsearchall(env.subst_path(path), - must_exist = 0, - clazz = SCons.Node.FS.Dir, - cwd = dir)) + dir = dir or env.fs._cwd + path = SCons.PathList.PathList(path).subst_path(env, target, source) + return tuple(dir.Rfindalldirs(path)) + + class Base: """ @@ -79,10 +89,12 @@ class Base: function, name = "NONE", argument = _null, - skeys = [], + skeys = _null, path_function = None, - node_class = SCons.Node.FS.Entry, - node_factory = SCons.Node.FS.default_fs.File, + # Node.FS.Base so that, by default, it's okay for a + # scanner to return a Dir, File or Entry. + node_class = SCons.Node.FS.Base, + node_factory = None, scan_check = None, recursive = None): """ @@ -100,10 +112,14 @@ class Base: which scanner should be used for a given Node. In the case of File nodes, for example, the 'skeys' would be file suffixes. - 'path_function' - a function that takes one to three arguments - (a construction environment, optional directory, and optional - argument for this instance) and returns a tuple of the - directories that can be searched for implicit dependency files. + 'path_function' - a function that takes four or five arguments + (a construction environment, Node for the directory containing + the SConscript file that defined the primary target, list of + target nodes, list of source nodes, and optional argument for + this instance) and returns a tuple of the directories that can + be searched for implicit dependency files. May also return a + callable() which is called with no args and returns the tuple + (supporting Bindable class). 'node_class' - the class of Nodes which this scan will return. If node_class is None, then this scanner will not enforce any @@ -118,14 +134,19 @@ class Base: this node really needs to be scanned. 'recursive' - specifies that this scanner should be invoked - recursively on the implicit dependencies it returns (the - canonical example being #include lines in C source files). - - The scanner function's first argument will be the name of a file - that should be scanned for dependencies, the second argument will - be an Environment object, the third argument will be the value - passed into 'argument', and the returned list should contain the - Nodes for all the direct dependencies of the file. + recursively on all of the implicit dependencies it returns + (the canonical example being #include lines in C source files). + May be a callable, which will be called to filter the list + of nodes found to select a subset for recursive scanning + (the canonical example being only recursively scanning + subdirectories within a directory). + + The scanner function's first argument will be a Node that should + be scanned for dependencies, the second argument will be an + Environment object, the third argument will be the tuple of paths + returned by the path_function, and the fourth argument will be + the value passed into 'argument', and the returned list should + contain the Nodes for all the direct dependencies of the file. Examples: @@ -146,19 +167,31 @@ class Base: self.path_function = path_function self.name = name self.argument = argument + + if skeys is _null: + if SCons.Util.is_Dict(function): + skeys = function.keys() + else: + skeys = [] self.skeys = skeys + self.node_class = node_class self.node_factory = node_factory self.scan_check = scan_check - self.recursive = recursive + if callable(recursive): + self.recurse_nodes = recursive + elif recursive: + self.recurse_nodes = self._recurse_all_nodes + else: + self.recurse_nodes = self._recurse_no_nodes - def path(self, env, dir = None): + def path(self, env, dir=None, target=None, source=None): if not self.path_function: return () if not self.argument is _null: - return self.path_function(env, dir, self.argument) + return self.path_function(env, dir, target, source, self.argument) else: - return self.path_function(env, dir) + return self.path_function(env, dir, target, source) def __call__(self, node, env, path = ()): """ @@ -170,47 +203,85 @@ class Base: if self.scan_check and not self.scan_check(node, env): return [] + self = self.select(node) + if not self.argument is _null: list = self.function(node, env, path, self.argument) else: list = self.function(node, env, path) + kw = {} if hasattr(node, 'dir'): kw['directory'] = node.dir + node_factory = env.get_factory(self.node_factory) nodes = [] for l in list: if self.node_class and not isinstance(l, self.node_class): - l = apply(self.node_factory, (l,), kw) + l = node_factory(l, **kw) nodes.append(l) return nodes def __cmp__(self, other): - return cmp(self.__dict__, other.__dict__) + try: + return cmp(self.__dict__, other.__dict__) + except AttributeError: + # other probably doesn't have a __dict__ + return cmp(self.__dict__, other) def __hash__(self): - return hash(repr(self)) + return id(self) + + def __str__(self): + return self.name def add_skey(self, skey): """Add a skey to the list of skeys""" self.skeys.append(skey) def get_skeys(self, env=None): - if SCons.Util.is_String(self.skeys): + if env and SCons.Util.is_String(self.skeys): return env.subst_list(self.skeys)[0] return self.skeys def select(self, node): - return self + if SCons.Util.is_Dict(self.function): + key = node.scanner_key() + try: + return self.function[key] + except KeyError: + return None + else: + return self + + def _recurse_all_nodes(self, nodes): + return nodes + + def _recurse_no_nodes(self, nodes): + return [] + + recurse_nodes = _recurse_no_nodes + + def add_scanner(self, skey, scanner): + self.function[skey] = scanner + self.add_skey(skey) class Selector(Base): """ A class for selecting a more specific scanner based on the scanner_key() (suffix) for a specific Node. + + TODO: This functionality has been moved into the inner workings of + the Base class, and this class will be deprecated at some point. + (It was never exposed directly as part of the public interface, + although it is used by the Scanner() factory function that was + used by various Tool modules and therefore was likely a template + for custom modules that may be out there.) """ def __init__(self, dict, *args, **kw): - Base.__init__(self, (None,)+args, kw) + Base.__init__(self, None, *args, **kw) self.dict = dict + self.skeys = dict.keys() def __call__(self, node, env, path = ()): return self.select(node)(node, env, path) @@ -223,6 +294,7 @@ class Selector(Base): def add_scanner(self, skey, scanner): self.dict[skey] = scanner + self.add_skey(skey) class Current(Base): @@ -234,11 +306,9 @@ class Current(Base): def __init__(self, *args, **kw): def current_check(node, env): - calc = env.get_calculator() - c = not node.has_builder() or node.current(env.get_calculator()) - return c + return not node.has_builder() or node.is_up_to_date() kw['scan_check'] = current_check - apply(Base.__init__, (self,) + args, kw) + Base.__init__(self, *args, **kw) class Classic(Current): """ @@ -247,76 +317,70 @@ class Classic(Current): regular expressions to find the includes. Note that in order for this to work "out of the box" (without - overriding the find_include() method), the regular expression passed - to the constructor must return the name of the include file in group - 0. + overriding the find_include() and sort_key() methods), the regular + expression passed to the constructor must return the name of the + include file in group 0. """ - def __init__(self, name, suffixes, path_variable, regex, - fs=SCons.Node.FS.default_fs, *args, **kw): + def __init__(self, name, suffixes, path_variable, regex, *args, **kw): self.cre = re.compile(regex, re.M) - self.fs = fs - def _scan(node, env, path, self=self, fs=fs): - return self.scan(node, env, path) + def _scan(node, env, path=(), self=self): + node = node.rfile() + if not node.exists(): + return [] + return self.scan(node, path) kw['function'] = _scan - kw['path_function'] = FindPathDirs(path_variable, fs) + kw['path_function'] = FindPathDirs(path_variable) kw['recursive'] = 1 kw['skeys'] = suffixes kw['name'] = name - apply(Current.__init__, (self,) + args, kw) + Current.__init__(self, *args, **kw) def find_include(self, include, source_dir, path): - n = SCons.Node.FS.find_file(include, (source_dir,) + path, self.fs.File) + n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path)) return n, include - def scan(self, node, env, path=()): - node = node.rfile() + def sort_key(self, include): + return SCons.Node.FS._my_normcase(include) - if not node.exists(): - return [] + def find_include_names(self, node): + return self.cre.findall(node.get_text_contents()) + + def scan(self, node, path=()): # cache the includes list in node so we only scan it once: - if node.includes != None: + if node.includes is not None: includes = node.includes else: - includes = self.cre.findall(node.get_contents()) - node.includes = includes - + includes = self.find_include_names (node) + # Intern the names of the include files. Saves some memory + # if the same header is included many times. + node.includes = list(map(SCons.Util.silent_intern, includes)) + + # This is a hand-coded DSU (decorate-sort-undecorate, or + # Schwartzian transform) pattern. The sort key is the raw name + # of the file as specifed on the #include line (including the + # " or <, since that may affect what file is found), which lets + # us keep the sort order constant regardless of whether the file + # is actually found in a Repository or locally. nodes = [] source_dir = node.get_dir() + if callable(path): + path = path() for include in includes: n, i = self.find_include(include, source_dir, path) - if not n is None: - nodes.append(n) - else: + if n is None: SCons.Warnings.warn(SCons.Warnings.DependencyWarning, "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) + else: + nodes.append((self.sort_key(include), n)) - # Schwartzian transform from the Python FAQ Wizard - def st(List, Metric): - def pairing(element, M = Metric): - return (M(element), element) - def stripit(pair): - return pair[1] - paired = map(pairing, List) - paired.sort() - return map(stripit, paired) - - def normalize(node): - # We don't want the order of includes to be - # modified by case changes on case insensitive OSes, so - # normalize the case of the filename here: - # (see test/win32pathmadness.py for a test of this) - return SCons.Node.FS._my_normcase(str(node)) - - transformed = st(nodes, normalize) - # print "Classic: " + str(node) + " => " + str(map(lambda x: str(x),list(transformed))) - return transformed + return [pair[1] for pair in sorted(nodes)] class ClassicCPP(Classic): """ @@ -330,11 +394,20 @@ class ClassicCPP(Classic): """ def find_include(self, include, source_dir, path): if include[0] == '"': - n = SCons.Node.FS.find_file(include[1], - (source_dir,) + path, - self.fs.File) + paths = (source_dir,) + tuple(path) else: - n = SCons.Node.FS.find_file(include[1], - path + (source_dir,), - self.fs.File) - return n, include[1] + paths = tuple(path) + (source_dir,) + + n = SCons.Node.FS.find_file(include[1], paths) + + i = SCons.Util.silent_intern(include[1]) + return n, i + + def sort_key(self, include): + return SCons.Node.FS._my_normcase(' '.join(include)) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: