Fix use of timestamps with --implicit-cache. (Anthony Roach)
[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__
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 import re
33
34 import SCons.Node.FS
35 import SCons.Sig
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 FindPathDirs:
47     """A class to bind a specific *PATH variable name and the fs object
48     to a function that will return all of the *path directories."""
49     def __init__(self, variable, fs):
50         self.variable = variable
51         self.fs = fs
52     def __call__(self, env, dir, argument=None):
53         try:
54             path = env[self.variable]
55         except KeyError:
56             return ()
57
58         return tuple(self.fs.Rsearchall(env.subst_path(path),
59                                         must_exist = 0,
60                                         clazz = SCons.Node.FS.Dir,
61                                         cwd = dir))
62
63 class Base:
64     """
65     The base class for dependency scanners.  This implements
66     straightforward, single-pass scanning of a single file.
67     """
68
69     def __init__(self,
70                  function,
71                  name = "NONE",
72                  argument = _null,
73                  skeys = [],
74                  path_function = None,
75                  node_class = SCons.Node.FS.Entry,
76                  node_factory = SCons.Node.FS.default_fs.File,
77                  scan_check = None,
78                  recursive = None):
79         """
80         Construct a new scanner object given a scanner function.
81
82         'function' - a scanner function taking two or three
83         arguments and returning a list of strings.
84
85         'name' - a name for identifying this scanner object.
86
87         'argument' - an optional argument that, if specified, will be
88         passed to both the scanner function and the path_function.
89
90         'skeys' - an optional list argument that can be used to determine
91         which scanner should be used for a given Node. In the case of File
92         nodes, for example, the 'skeys' would be file suffixes.
93
94         'path_function' - a function that takes one to three arguments
95         (a construction environment, optional directory, and optional
96         argument for this instance) and returns a tuple of the
97         directories that can be searched for implicit dependency files.
98
99         'node_class' - the class of Nodes which this scan will return.
100         If node_class is None, then this scanner will not enforce any
101         Node conversion and will return the raw results from the
102         underlying scanner function.
103
104         'node_factory' - the factory function to be called to translate
105         the raw results returned by the scanner function into the
106         expected node_class objects.
107
108         'scan_check' - a function to be called to first check whether
109         this node really needs to be scanned.
110
111         'recursive' - specifies that this scanner should be invoked
112         recursively on the implicit dependencies it returns (the
113         canonical example being #include lines in C source files).
114
115         The scanner function's first argument will be the name of a file
116         that should be scanned for dependencies, the second argument will
117         be an Environment object, the third argument will be the value
118         passed into 'argument', and the returned list should contain the
119         Nodes for all the direct dependencies of the file.
120
121         Examples:
122
123         s = Scanner(my_scanner_function)
124
125         s = Scanner(function = my_scanner_function)
126
127         s = Scanner(function = my_scanner_function, argument = 'foo')
128
129         """
130
131         # Note: this class could easily work with scanner functions that take
132         # something other than a filename as an argument (e.g. a database
133         # node) and a dependencies list that aren't file names. All that
134         # would need to be changed is the documentation.
135
136         self.function = function
137         self.path_function = path_function
138         self.name = name
139         self.argument = argument
140         self.skeys = skeys
141         self.node_class = node_class
142         self.node_factory = node_factory
143         self.scan_check = scan_check
144         self.recursive = recursive
145
146     def path(self, env, dir = None):
147         if not self.path_function:
148             return ()
149         if not self.argument is _null:
150             return self.path_function(env, dir, self.argument)
151         else:
152             return self.path_function(env, dir)
153
154     def __call__(self, node, env, path = ()):
155         """
156         This method scans a single object. 'node' is the node
157         that will be passed to the scanner function, and 'env' is the
158         environment that will be passed to the scanner function. A list of
159         direct dependency nodes for the specified node will be returned.
160         """
161         if self.scan_check and not self.scan_check(node, env):
162             return []
163
164         if not self.argument is _null:
165             list = self.function(node, env, path, self.argument)
166         else:
167             list = self.function(node, env, path)
168         kw = {}
169         if hasattr(node, 'dir'):
170             kw['directory'] = node.dir
171         nodes = []
172         for l in list:
173             if self.node_class and not isinstance(l, self.node_class):
174                 l = apply(self.node_factory, (l,), kw)
175             nodes.append(l)
176         return nodes
177
178     def __cmp__(self, other):
179         return cmp(self.__dict__, other.__dict__)
180
181     def __hash__(self):
182         return hash(repr(self))
183
184     def add_skey(self, skey):
185         """Add a skey to the list of skeys"""
186         self.skeys.append(skey)
187
188     def get_skeys(self, env=None):
189         if SCons.Util.is_String(self.skeys):
190             return env.subst_list(self.skeys)[0]
191         return self.skeys
192
193
194 class Current(Base):
195     """
196     A class for scanning files that are source files (have no builder)
197     or are derived files and are current (which implies that they exist,
198     either locally or in a repository).
199     """
200
201     def __init__(self, *args, **kw):
202         def current_check(node, env):
203             c = not node.has_builder() or node.current(env.get_calculator())
204             return c
205         kw['scan_check'] = current_check
206         apply(Base.__init__, (self,) + args, kw)
207
208 class Classic(Current):
209     """
210     A Scanner subclass to contain the common logic for classic CPP-style
211     include scanning, but which can be customized to use different
212     regular expressions to find the includes.
213
214     Note that in order for this to work "out of the box" (without
215     overriding the find_include() method), the regular expression passed
216     to the constructor must return the name of the include file in group
217     0.
218     """
219
220     def __init__(self, name, suffixes, path_variable, regex,
221                  fs=SCons.Node.FS.default_fs, *args, **kw):
222
223         self.cre = re.compile(regex, re.M)
224         self.fs = fs
225
226         def _scan(node, env, path, self=self, fs=fs):
227             return self.scan(node, env, path)
228
229         kw['function'] = _scan
230         kw['path_function'] = FindPathDirs(path_variable, fs)
231         kw['recursive'] = 1
232         kw['skeys'] = suffixes
233         kw['name'] = name
234
235         apply(Current.__init__, (self,) + args, kw)
236
237     def find_include(self, include, source_dir, path):
238         n = SCons.Node.FS.find_file(include, (source_dir,) + path, self.fs.File)
239         return n, include
240
241     def scan(self, node, env, path=()):
242         node = node.rfile()
243
244         if not node.exists():
245             return []
246
247         # cache the includes list in node so we only scan it once:
248         if node.includes != None:
249             includes = node.includes
250         else:
251             includes = self.cre.findall(node.get_contents())
252             node.includes = includes
253
254         nodes = []
255         source_dir = node.get_dir()
256         for include in includes:
257             n, i = self.find_include(include, source_dir, path)
258
259             if not n is None:
260                 nodes.append(n)
261             else:
262                 SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
263                                     "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
264
265         # Schwartzian transform from the Python FAQ Wizard
266         def st(List, Metric):
267             def pairing(element, M = Metric):
268                 return (M(element), element)
269             def stripit(pair):
270                 return pair[1]
271             paired = map(pairing, List)
272             paired.sort()
273             return map(stripit, paired)
274
275         def normalize(node):
276             # We don't want the order of includes to be
277             # modified by case changes on case insensitive OSes, so
278             # normalize the case of the filename here:
279             # (see test/win32pathmadness.py for a test of this)
280             return SCons.Node.FS._my_normcase(str(node))
281
282         transformed = st(nodes, normalize)
283         # print "Classic: " + str(node) + " => " + str(map(lambda x: str(x),list(transformed)))
284         return transformed
285
286 class ClassicCPP(Classic):
287     """
288     A Classic Scanner subclass which takes into account the type of
289     bracketing used to include the file, and uses classic CPP rules
290     for searching for the files based on the bracketing.
291
292     Note that in order for this to work, the regular expression passed
293     to the constructor must return the leading bracket in group 0, and
294     the contained filename in group 1.
295     """
296     def find_include(self, include, source_dir, path):
297         if include[0] == '"':
298             n = SCons.Node.FS.find_file(include[1],
299                                         (source_dir,) + path,
300                                         self.fs.File)
301         else:
302             n = SCons.Node.FS.find_file(include[1],
303                                         path + (source_dir,),
304                                         self.fs.File)
305         return n, include[1]