Lookup implicit dependencies relative to the directory of the node in which the depen...
[scons.git] / src / engine / SCons / Scanner / __init__.py
1 """SCons.Scanner
2
3 The Scanner package for the SCons software construction utility.
4
5 """
6
7 #
8 # Copyright (c) 2001, 2002 Steven Knight
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32 __version__ = "__VERSION__"
33
34
35 import SCons.Node.FS
36 import SCons.Util
37
38
39 class _Null:
40     pass
41
42 # This is used instead of None as a default argument value so None can be
43 # used as an actual argument value.
44 _null = _Null
45
46 class Base:
47     """
48     The base class for dependency scanners.  This implements
49     straightforward, single-pass scanning of a single file.
50     """
51
52     def __init__(self,
53                  function,
54                  name = "NONE",
55                  argument = _null,
56                  skeys = [],
57                  node_factory = SCons.Node.FS.default_fs.File):
58         """
59         Construct a new scanner object given a scanner function.
60
61         'function' - a scanner function taking two or three arguments and
62         returning a list of strings.
63
64         'name' - a name for identifying this scanner object.
65
66         'argument' - an optional argument that will be passed to the
67         scanner function if it is given.
68
69         'skeys; - an optional list argument that can be used to determine
70         which scanner should be used for a given Node. In the case of File
71         nodes, for example, the 'skeys' would be file suffixes.
72
73         The scanner function's first argument will be the name of a file
74         that should be scanned for dependencies, the second argument will
75         be an Environment object, the third argument will be the value
76         passed into 'argument', and the returned list should contain the
77         Nodes for all the direct dependencies of the file.
78
79         Examples:
80
81         s = Scanner(my_scanner_function)
82         
83         s = Scanner(function = my_scanner_function)
84
85         s = Scanner(function = my_scanner_function, argument = 'foo')
86
87         """
88
89         # Note: this class could easily work with scanner functions that take
90         # something other than a filename as an argument (e.g. a database
91         # node) and a dependencies list that aren't file names. All that
92         # would need to be changed is the documentation.
93
94         self.function = function
95         self.name = name
96         self.argument = argument
97         self.skeys = skeys
98         self.node_factory = node_factory
99
100     def scan(self, node, env):
101         """
102         This method scans a single object. 'node' is the node
103         that will be passed to the scanner function, and 'env' is the
104         environment that will be passed to the scanner function. A list of
105         direct dependency nodes for the specified node will be returned.
106         """
107
108         if not self.argument is _null:
109             list = self.function(node, env, self.argument)
110         else:
111             list = self.function(node, env)
112         kw = {}
113         if hasattr(node, 'dir'):
114             kw['directory'] = node.dir
115         nodes = []
116         for l in list:
117             if not isinstance(l, SCons.Node.FS.Entry):
118                 l = apply(self.node_factory, (l,), kw)
119             nodes.append(l)
120         return nodes
121
122     def instance(self, env):
123         """
124         Return an instance of a Scanner object for use in scanning.
125
126         In the base class, we just return the scanner itself.
127         Other Scanner classes may use this to clone copies and/or
128         return unique instances as needed.
129         """
130         return self
131
132     def __cmp__(self, other):
133         return cmp(self.__dict__, other.__dict__)
134
135     def __hash__(self):
136         return hash(None)
137
138 class Recursive(Base):
139     """
140     The class for recursive dependency scanning.  This will
141     re-scan any new files returned by each call to the
142     underlying scanning function, and return the aggregate
143     list of all dependencies.
144     """
145
146     def scan(self, node, env):
147         """
148         This method does the actual scanning. 'node' is the node
149         that will be passed to the scanner function, and 'env' is the
150         environment that will be passed to the scanner function. An
151         aggregate list of dependency nodes for the specified filename
152         and any of its scanned dependencies will be returned.
153         """
154
155         nodes = [node]
156         seen = {node : 0}
157         deps = []
158         while nodes:
159             n = nodes.pop(0)
160             d = filter(lambda x, seen=seen: not seen.has_key(x),
161                        Base.scan(self, n, env))
162             if d:
163                 deps.extend(d)
164                 nodes.extend(d)
165                 for n in d:
166                     seen[n] = 0
167         return deps