Merged revisions 1582-1665 via svnmerge from
[scons.git] / src / engine / SCons / Node / FS.py
1 """scons.Node.FS
2
3 File system nodes.
4
5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
7
8 This holds a "default_fs" variable that should be initialized with an FS
9 that can be used by scripts or modules looking for the canonical default.
10
11 """
12
13 #
14 # __COPYRIGHT__
15 #
16 # Permission is hereby granted, free of charge, to any person obtaining
17 # a copy of this software and associated documentation files (the
18 # "Software"), to deal in the Software without restriction, including
19 # without limitation the rights to use, copy, modify, merge, publish,
20 # distribute, sublicense, and/or sell copies of the Software, and to
21 # permit persons to whom the Software is furnished to do so, subject to
22 # the following conditions:
23 #
24 # The above copyright notice and this permission notice shall be included
25 # in all copies or substantial portions of the Software.
26 #
27 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
28 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
29 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 #
35
36 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
37
38 import os
39 import os.path
40 import shutil
41 import stat
42 import string
43 import sys
44 import time
45 import cStringIO
46
47 import SCons.Action
48 from SCons.Debug import logInstanceCreation
49 import SCons.Errors
50 import SCons.Node
51 import SCons.Sig.MD5
52 import SCons.Subst
53 import SCons.Util
54 import SCons.Warnings
55
56 # The max_drift value:  by default, use a cached signature value for
57 # any file that's been untouched for more than two days.
58 default_max_drift = 2*24*60*60
59
60 #
61 # We stringify these file system Nodes a lot.  Turning a file system Node
62 # into a string is non-trivial, because the final string representation
63 # can depend on a lot of factors:  whether it's a derived target or not,
64 # whether it's linked to a repository or source directory, and whether
65 # there's duplication going on.  The normal technique for optimizing
66 # calculations like this is to memoize (cache) the string value, so you
67 # only have to do the calculation once.
68 #
69 # A number of the above factors, however, can be set after we've already
70 # been asked to return a string for a Node, because a Repository() or
71 # BuildDir() call or the like may not occur until later in SConscript
72 # files.  So this variable controls whether we bother trying to save
73 # string values for Nodes.  The wrapper interface can set this whenever
74 # they're done mucking with Repository and BuildDir and the other stuff,
75 # to let this module know it can start returning saved string values
76 # for Nodes.
77 #
78 Save_Strings = None
79
80 def save_strings(val):
81     global Save_Strings
82     Save_Strings = val
83
84 #
85 # SCons.Action objects for interacting with the outside world.
86 #
87 # The Node.FS methods in this module should use these actions to
88 # create and/or remove files and directories; they should *not* use
89 # os.{link,symlink,unlink,mkdir}(), etc., directly.
90 #
91 # Using these SCons.Action objects ensures that descriptions of these
92 # external activities are properly displayed, that the displays are
93 # suppressed when the -s (silent) option is used, and (most importantly)
94 # the actions are disabled when the the -n option is used, in which case
95 # there should be *no* changes to the external file system(s)...
96 #
97
98 if hasattr(os, 'link'):
99     def _hardlink_func(fs, src, dst):
100         # If the source is a symlink, we can't just hard-link to it
101         # because a relative symlink may point somewhere completely
102         # different.  We must disambiguate the symlink and then
103         # hard-link the final destination file.
104         while fs.islink(src):
105             link = fs.readlink(src)
106             if not os.path.isabs(link):
107                 src = link
108             else:
109                 src = os.path.join(os.path.dirname(src), link)
110         fs.link(src, dst)
111 else:
112     _hardlink_func = None
113
114 if hasattr(os, 'symlink'):
115     def _softlink_func(fs, src, dst):
116         fs.symlink(src, dst)
117 else:
118     _softlink_func = None
119
120 def _copy_func(fs, src, dest):
121     shutil.copy2(src, dest)
122     st = fs.stat(src)
123     fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
124
125
126 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
127                     'hard-copy', 'soft-copy', 'copy']
128
129 Link_Funcs = [] # contains the callables of the specified duplication style
130
131 def set_duplicate(duplicate):
132     # Fill in the Link_Funcs list according to the argument
133     # (discarding those not available on the platform).
134
135     # Set up the dictionary that maps the argument names to the
136     # underlying implementations.  We do this inside this function,
137     # not in the top-level module code, so that we can remap os.link
138     # and os.symlink for testing purposes.
139     link_dict = {
140         'hard' : _hardlink_func,
141         'soft' : _softlink_func,
142         'copy' : _copy_func
143     }
144
145     if not duplicate in Valid_Duplicates:
146         raise SCons.Errors.InternalError, ("The argument of set_duplicate "
147                                            "should be in Valid_Duplicates")
148     global Link_Funcs
149     Link_Funcs = []
150     for func in string.split(duplicate,'-'):
151         if link_dict[func]:
152             Link_Funcs.append(link_dict[func])
153
154 def LinkFunc(target, source, env):
155     # Relative paths cause problems with symbolic links, so
156     # we use absolute paths, which may be a problem for people
157     # who want to move their soft-linked src-trees around. Those
158     # people should use the 'hard-copy' mode, softlinks cannot be
159     # used for that; at least I have no idea how ...
160     src = source[0].abspath
161     dest = target[0].abspath
162     dir, file = os.path.split(dest)
163     if dir and not target[0].fs.isdir(dir):
164         os.makedirs(dir)
165     if not Link_Funcs:
166         # Set a default order of link functions.
167         set_duplicate('hard-soft-copy')
168     fs = source[0].fs
169     # Now link the files with the previously specified order.
170     for func in Link_Funcs:
171         try:
172             func(fs, src, dest)
173             break
174         except (IOError, OSError):
175             # An OSError indicates something happened like a permissions
176             # problem or an attempt to symlink across file-system
177             # boundaries.  An IOError indicates something like the file
178             # not existing.  In either case, keeping trying additional
179             # functions in the list and only raise an error if the last
180             # one failed.
181             if func == Link_Funcs[-1]:
182                 # exception of the last link method (copy) are fatal
183                 raise
184             else:
185                 pass
186     return 0
187
188 Link = SCons.Action.Action(LinkFunc, None)
189 def LocalString(target, source, env):
190     return 'Local copy of %s from %s' % (target[0], source[0])
191
192 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
193
194 def UnlinkFunc(target, source, env):
195     t = target[0]
196     t.fs.unlink(t.abspath)
197     return 0
198
199 Unlink = SCons.Action.Action(UnlinkFunc, None)
200
201 def MkdirFunc(target, source, env):
202     t = target[0]
203     if not t.exists():
204         t.fs.mkdir(t.abspath)
205     return 0
206
207 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
208
209 MkdirBuilder = None
210
211 def get_MkdirBuilder():
212     global MkdirBuilder
213     if MkdirBuilder is None:
214         import SCons.Builder
215         import SCons.Defaults
216         # "env" will get filled in by Executor.get_build_env()
217         # calling SCons.Defaults.DefaultEnvironment() when necessary.
218         MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
219                                              env = None,
220                                              explain = None,
221                                              is_explicit = None,
222                                              target_scanner = SCons.Defaults.DirEntryScanner,
223                                              name = "MkdirBuilder")
224     return MkdirBuilder
225
226 def CacheRetrieveFunc(target, source, env):
227     t = target[0]
228     fs = t.fs
229     cachedir, cachefile = t.cachepath()
230     if not fs.exists(cachefile):
231         fs.CacheDebug('CacheRetrieve(%s):  %s not in cache\n', t, cachefile)
232         return 1
233     fs.CacheDebug('CacheRetrieve(%s):  retrieving from %s\n', t, cachefile)
234     if SCons.Action.execute_actions:
235         fs.copy2(cachefile, t.path)
236         st = fs.stat(cachefile)
237         fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
238     return 0
239
240 def CacheRetrieveString(target, source, env):
241     t = target[0]
242     cachedir, cachefile = t.cachepath()
243     if t.fs.exists(cachefile):
244         return "Retrieved `%s' from cache" % t.path
245     return None
246
247 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
248
249 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
250
251 def CachePushFunc(target, source, env):
252     t = target[0]
253     fs = t.fs
254     cachedir, cachefile = t.cachepath()
255     if fs.exists(cachefile):
256         # Don't bother copying it if it's already there.  Note that
257         # usually this "shouldn't happen" because if the file already
258         # existed in cache, we'd have retrieved the file from there,
259         # not built it.  This can happen, though, in a race, if some
260         # other person running the same build pushes their copy to
261         # the cache after we decide we need to build it but before our
262         # build completes.
263         fs.CacheDebug('CachePush(%s):  %s already exists in cache\n', t, cachefile)
264         return
265
266     fs.CacheDebug('CachePush(%s):  pushing to %s\n', t, cachefile)
267
268     if not fs.isdir(cachedir):
269         fs.makedirs(cachedir)
270
271     tempfile = cachefile+'.tmp'
272     try:
273         fs.copy2(t.path, tempfile)
274         fs.rename(tempfile, cachefile)
275         st = fs.stat(t.path)
276         fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
277     except (IOError, OSError):
278         # It's possible someone else tried writing the file at the
279         # same time we did, or else that there was some problem like
280         # the CacheDir being on a separate file system that's full.
281         # In any case, inability to push a file to cache doesn't affect
282         # the correctness of the build, so just print a warning.
283         SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
284                             "Unable to copy %s to cache. Cache file is %s"
285                                 % (str(target), cachefile))
286
287 CachePush = SCons.Action.Action(CachePushFunc, None)
288
289 class _Null:
290     pass
291
292 _null = _Null()
293
294 DefaultSCCSBuilder = None
295 DefaultRCSBuilder = None
296
297 def get_DefaultSCCSBuilder():
298     global DefaultSCCSBuilder
299     if DefaultSCCSBuilder is None:
300         import SCons.Builder
301         # "env" will get filled in by Executor.get_build_env()
302         # calling SCons.Defaults.DefaultEnvironment() when necessary.
303         act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
304         DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
305                                                    env = None,
306                                                    name = "DefaultSCCSBuilder")
307     return DefaultSCCSBuilder
308
309 def get_DefaultRCSBuilder():
310     global DefaultRCSBuilder
311     if DefaultRCSBuilder is None:
312         import SCons.Builder
313         # "env" will get filled in by Executor.get_build_env()
314         # calling SCons.Defaults.DefaultEnvironment() when necessary.
315         act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
316         DefaultRCSBuilder = SCons.Builder.Builder(action = act,
317                                                   env = None,
318                                                   name = "DefaultRCSBuilder")
319     return DefaultRCSBuilder
320
321 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
322 _is_cygwin = sys.platform == "cygwin"
323 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
324     def _my_normcase(x):
325         return x
326 else:
327     def _my_normcase(x):
328         return string.upper(x)
329
330
331
332 class DiskChecker:
333     def __init__(self, type, do, ignore):
334         self.type = type
335         self.do = do
336         self.ignore = ignore
337         self.set_do()
338     def set_do(self):
339         self.__call__ = self.do
340     def set_ignore(self):
341         self.__call__ = self.ignore
342     def set(self, list):
343         if self.type in list:
344             self.set_do()
345         else:
346             self.set_ignore()
347
348 def do_diskcheck_match(node, predicate, errorfmt):
349     path = node.abspath
350     if predicate(path):
351         raise TypeError, errorfmt % path
352
353 def ignore_diskcheck_match(node, predicate, errorfmt):
354     pass
355
356 def do_diskcheck_rcs(node, name):
357     try:
358         rcs_dir = node.rcs_dir
359     except AttributeError:
360         if node.entry_exists_on_disk('RCS'):
361             rcs_dir = node.Dir('RCS')
362         else:
363             rcs_dir = None
364         node.rcs_dir = rcs_dir
365     if rcs_dir:
366         return rcs_dir.entry_exists_on_disk(name+',v')
367     return None
368
369 def ignore_diskcheck_rcs(node, name):
370     return None
371
372 def do_diskcheck_sccs(node, name):
373     try:
374         sccs_dir = node.sccs_dir
375     except AttributeError:
376         if node.entry_exists_on_disk('SCCS'):
377             sccs_dir = node.Dir('SCCS')
378         else:
379             sccs_dir = None
380         node.sccs_dir = sccs_dir
381     if sccs_dir:
382         return sccs_dir.entry_exists_on_disk('s.'+name)
383     return None
384
385 def ignore_diskcheck_sccs(node, name):
386     return None
387
388 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
389 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
390 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
391
392 diskcheckers = [
393     diskcheck_match,
394     diskcheck_rcs,
395     diskcheck_sccs,
396 ]
397
398 def set_diskcheck(list):
399     for dc in diskcheckers:
400         dc.set(list)
401
402 def diskcheck_types():
403     return map(lambda dc: dc.type, diskcheckers)
404
405
406
407 class EntryProxy(SCons.Util.Proxy):
408     def __get_abspath(self):
409         entry = self.get()
410         return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
411                                              entry.name + "_abspath")
412
413     def __get_filebase(self):
414         name = self.get().name
415         return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
416                                              name + "_filebase")
417
418     def __get_suffix(self):
419         name = self.get().name
420         return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
421                                              name + "_suffix")
422
423     def __get_file(self):
424         name = self.get().name
425         return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
426
427     def __get_base_path(self):
428         """Return the file's directory and file name, with the
429         suffix stripped."""
430         entry = self.get()
431         return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
432                                              entry.name + "_base")
433
434     def __get_posix_path(self):
435         """Return the path with / as the path separator,
436         regardless of platform."""
437         if os.sep == '/':
438             return self
439         else:
440             entry = self.get()
441             r = string.replace(entry.get_path(), os.sep, '/')
442             return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
443
444     def __get_windows_path(self):
445         """Return the path with \ as the path separator,
446         regardless of platform."""
447         if os.sep == '\\':
448             return self
449         else:
450             entry = self.get()
451             r = string.replace(entry.get_path(), os.sep, '\\')
452             return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
453
454     def __get_srcnode(self):
455         return EntryProxy(self.get().srcnode())
456
457     def __get_srcdir(self):
458         """Returns the directory containing the source node linked to this
459         node via BuildDir(), or the directory of this node if not linked."""
460         return EntryProxy(self.get().srcnode().dir)
461
462     def __get_rsrcnode(self):
463         return EntryProxy(self.get().srcnode().rfile())
464
465     def __get_rsrcdir(self):
466         """Returns the directory containing the source node linked to this
467         node via BuildDir(), or the directory of this node if not linked."""
468         return EntryProxy(self.get().srcnode().rfile().dir)
469
470     def __get_dir(self):
471         return EntryProxy(self.get().dir)
472     
473     dictSpecialAttrs = { "base"     : __get_base_path,
474                          "posix"    : __get_posix_path,
475                          "windows"  : __get_windows_path,
476                          "win32"    : __get_windows_path,
477                          "srcpath"  : __get_srcnode,
478                          "srcdir"   : __get_srcdir,
479                          "dir"      : __get_dir,
480                          "abspath"  : __get_abspath,
481                          "filebase" : __get_filebase,
482                          "suffix"   : __get_suffix,
483                          "file"     : __get_file,
484                          "rsrcpath" : __get_rsrcnode,
485                          "rsrcdir"  : __get_rsrcdir,
486                        }
487
488     def __getattr__(self, name):
489         # This is how we implement the "special" attributes
490         # such as base, posix, srcdir, etc.
491         try:
492             attr_function = self.dictSpecialAttrs[name]
493         except KeyError:
494             try:
495                 attr = SCons.Util.Proxy.__getattr__(self, name)
496             except AttributeError:
497                 entry = self.get()
498                 classname = string.split(str(entry.__class__), '.')[-1]
499                 if classname[-2:] == "'>":
500                     # new-style classes report their name as:
501                     #   "<class 'something'>"
502                     # instead of the classic classes:
503                     #   "something"
504                     classname = classname[:-2]
505                 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
506             return attr
507         else:
508             return attr_function(self)
509
510 class Base(SCons.Node.Node):
511     """A generic class for file system entries.  This class is for
512     when we don't know yet whether the entry being looked up is a file
513     or a directory.  Instances of this class can morph into either
514     Dir or File objects by a later, more precise lookup.
515
516     Note: this class does not define __cmp__ and __hash__ for
517     efficiency reasons.  SCons does a lot of comparing of
518     Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
519     as fast as possible, which means we want to use Python's built-in
520     object identity comparisons.
521     """
522
523     def __init__(self, name, directory, fs):
524         """Initialize a generic Node.FS.Base object.
525         
526         Call the superclass initialization, take care of setting up
527         our relative and absolute paths, identify our parent
528         directory, and indicate that this node should use
529         signatures."""
530         if __debug__: logInstanceCreation(self, 'Node.FS.Base')
531         SCons.Node.Node.__init__(self)
532
533         self.name = name
534         self.fs = fs
535
536         assert directory, "A directory must be provided"
537
538         self.abspath = directory.entry_abspath(name)
539         if directory.path == '.':
540             self.path = name
541         else:
542             self.path = directory.entry_path(name)
543         if directory.tpath == '.':
544             self.tpath = name
545         else:
546             self.tpath = directory.entry_tpath(name)
547         self.path_elements = directory.path_elements + [self]
548
549         self.dir = directory
550         self.cwd = None # will hold the SConscript directory for target nodes
551         self.duplicate = directory.duplicate
552
553     def clear(self):
554         """Completely clear a Node.FS.Base object of all its cached
555         state (so that it can be re-evaluated by interfaces that do
556         continuous integration builds).
557         __cache_reset__
558         """
559         SCons.Node.Node.clear(self)
560
561     def get_dir(self):
562         return self.dir
563
564     def get_suffix(self):
565         "__cacheable__"
566         return SCons.Util.splitext(self.name)[1]
567
568     def rfile(self):
569         return self
570
571     def __str__(self):
572         """A Node.FS.Base object's string representation is its path
573         name."""
574         global Save_Strings
575         if Save_Strings:
576             return self._save_str()
577         return self._get_str()
578
579     def _save_str(self):
580         "__cacheable__"
581         return self._get_str()
582
583     def _get_str(self):
584         if self.duplicate or self.is_derived():
585             return self.get_path()
586         return self.srcnode().get_path()
587
588     rstr = __str__
589
590     def stat(self):
591         "__cacheable__"
592         try: return self.fs.stat(self.abspath)
593         except os.error: return None
594
595     def exists(self):
596         "__cacheable__"
597         return not self.stat() is None
598
599     def rexists(self):
600         "__cacheable__"
601         return self.rfile().exists()
602
603     def getmtime(self):
604         st = self.stat()
605         if st: return st[stat.ST_MTIME]
606         else: return None
607
608     def getsize(self):
609         st = self.stat()
610         if st: return st[stat.ST_SIZE]
611         else: return None
612
613     def isdir(self):
614         st = self.stat()
615         return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
616
617     def isfile(self):
618         st = self.stat()
619         return not st is None and stat.S_ISREG(st[stat.ST_MODE])
620
621     if hasattr(os, 'symlink'):
622         def islink(self):
623             try: st = self.fs.lstat(self.abspath)
624             except os.error: return 0
625             return stat.S_ISLNK(st[stat.ST_MODE])
626     else:
627         def islink(self):
628             return 0                    # no symlinks
629
630     def is_under(self, dir):
631         if self is dir:
632             return 1
633         else:
634             return self.dir.is_under(dir)
635
636     def set_local(self):
637         self._local = 1
638
639     def srcnode(self):
640         """If this node is in a build path, return the node
641         corresponding to its source file.  Otherwise, return
642         ourself.
643         __cacheable__"""
644         dir=self.dir
645         name=self.name
646         while dir:
647             if dir.srcdir:
648                 srcnode = self.fs.Entry(name, dir.srcdir,
649                                         klass=self.__class__)
650                 return srcnode
651             name = dir.name + os.sep + name
652             dir = dir.up()
653         return self
654
655     def get_path(self, dir=None):
656         """Return path relative to the current working directory of the
657         Node.FS.Base object that owns us."""
658         if not dir:
659             dir = self.fs.getcwd()
660         if self == dir:
661             return '.'
662         path_elems = self.path_elements
663         try: i = path_elems.index(dir)
664         except ValueError: pass
665         else: path_elems = path_elems[i+1:]
666         path_elems = map(lambda n: n.name, path_elems)
667         return string.join(path_elems, os.sep)
668
669     def set_src_builder(self, builder):
670         """Set the source code builder for this node."""
671         self.sbuilder = builder
672         if not self.has_builder():
673             self.builder_set(builder)
674
675     def src_builder(self):
676         """Fetch the source code builder for this node.
677
678         If there isn't one, we cache the source code builder specified
679         for the directory (which in turn will cache the value from its
680         parent directory, and so on up to the file system root).
681         """
682         try:
683             scb = self.sbuilder
684         except AttributeError:
685             scb = self.dir.src_builder()
686             self.sbuilder = scb
687         return scb
688
689     def get_abspath(self):
690         """Get the absolute path of the file."""
691         return self.abspath
692
693     def for_signature(self):
694         # Return just our name.  Even an absolute path would not work,
695         # because that can change thanks to symlinks or remapped network
696         # paths.
697         return self.name
698
699     def get_subst_proxy(self):
700         try:
701             return self._proxy
702         except AttributeError:
703             ret = EntryProxy(self)
704             self._proxy = ret
705             return ret
706
707     def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
708         return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
709
710     def RDirs(self, pathlist):
711         """Search for a list of directories in the Repository list."""
712         return self.fs.Rfindalldirs(pathlist, self.cwd)
713
714 class Entry(Base):
715     """This is the class for generic Node.FS entries--that is, things
716     that could be a File or a Dir, but we're just not sure yet.
717     Consequently, the methods in this class really exist just to
718     transform their associated object into the right class when the
719     time comes, and then call the same-named method in the transformed
720     class."""
721
722     def diskcheck_match(self):
723         pass
724
725     def disambiguate(self):
726         if self.isdir() or self.srcnode().isdir():
727             self.__class__ = Dir
728             self._morph()
729         else:
730             self.__class__ = File
731             self._morph()
732             self.clear()
733         return self
734
735     def rfile(self):
736         """We're a generic Entry, but the caller is actually looking for
737         a File at this point, so morph into one."""
738         self.__class__ = File
739         self._morph()
740         self.clear()
741         return File.rfile(self)
742
743     def scanner_key(self):
744         return self.get_suffix()
745
746     def get_contents(self):
747         """Fetch the contents of the entry.
748         
749         Since this should return the real contents from the file
750         system, we check to see into what sort of subclass we should
751         morph this Entry."""
752         if self.isfile():
753             self.__class__ = File
754             self._morph()
755             return self.get_contents()
756         if self.isdir():
757             self.__class__ = Dir
758             self._morph()
759             return self.get_contents()
760         if self.islink():
761             return ''             # avoid errors for dangling symlinks
762         raise AttributeError
763
764     def must_be_a_Dir(self):
765         """Called to make sure a Node is a Dir.  Since we're an
766         Entry, we can morph into one."""
767         self.__class__ = Dir
768         self._morph()
769         return self
770
771     # The following methods can get called before the Taskmaster has
772     # had a chance to call disambiguate() directly to see if this Entry
773     # should really be a Dir or a File.  We therefore use these to call
774     # disambiguate() transparently (from our caller's point of view).
775     #
776     # Right now, this minimal set of methods has been derived by just
777     # looking at some of the methods that will obviously be called early
778     # in any of the various Taskmasters' calling sequences, and then
779     # empirically figuring out which additional methods are necessary
780     # to make various tests pass.
781
782     def exists(self):
783         """Return if the Entry exists.  Check the file system to see
784         what we should turn into first.  Assume a file if there's no
785         directory."""
786         return self.disambiguate().exists()
787
788     def rel_path(self, other):
789         d = self.disambiguate()
790         if d.__class__ == Entry:
791             raise "rel_path() could not disambiguate File/Dir"
792         return d.rel_path(other)
793
794 # This is for later so we can differentiate between Entry the class and Entry
795 # the method of the FS class.
796 _classEntry = Entry
797
798
799 class LocalFS:
800
801     if SCons.Memoize.use_memoizer:
802         __metaclass__ = SCons.Memoize.Memoized_Metaclass
803     
804     # This class implements an abstraction layer for operations involving
805     # a local file system.  Essentially, this wraps any function in
806     # the os, os.path or shutil modules that we use to actually go do
807     # anything with or to the local file system.
808     #
809     # Note that there's a very good chance we'll refactor this part of
810     # the architecture in some way as we really implement the interface(s)
811     # for remote file system Nodes.  For example, the right architecture
812     # might be to have this be a subclass instead of a base class.
813     # Nevertheless, we're using this as a first step in that direction.
814     #
815     # We're not using chdir() yet because the calling subclass method
816     # needs to use os.chdir() directly to avoid recursion.  Will we
817     # really need this one?
818     #def chdir(self, path):
819     #    return os.chdir(path)
820     def chmod(self, path, mode):
821         return os.chmod(path, mode)
822     def copy2(self, src, dst):
823         return shutil.copy2(src, dst)
824     def exists(self, path):
825         return os.path.exists(path)
826     def getmtime(self, path):
827         return os.path.getmtime(path)
828     def getsize(self, path):
829         return os.path.getsize(path)
830     def isdir(self, path):
831         return os.path.isdir(path)
832     def isfile(self, path):
833         return os.path.isfile(path)
834     def link(self, src, dst):
835         return os.link(src, dst)
836     def lstat(self, path):
837         return os.lstat(path)
838     def listdir(self, path):
839         return os.listdir(path)
840     def makedirs(self, path):
841         return os.makedirs(path)
842     def mkdir(self, path):
843         return os.mkdir(path)
844     def rename(self, old, new):
845         return os.rename(old, new)
846     def stat(self, path):
847         return os.stat(path)
848     def symlink(self, src, dst):
849         return os.symlink(src, dst)
850     def open(self, path):
851         return open(path)
852     def unlink(self, path):
853         return os.unlink(path)
854
855     if hasattr(os, 'symlink'):
856         def islink(self, path):
857             return os.path.islink(path)
858     else:
859         def islink(self, path):
860             return 0                    # no symlinks
861
862     if hasattr(os, 'readlink'):
863         def readlink(self, file):
864             return os.readlink(file)
865     else:
866         def readlink(self, file):
867             return ''
868
869
870 if SCons.Memoize.use_old_memoization():
871     _FSBase = LocalFS
872     class LocalFS(SCons.Memoize.Memoizer, _FSBase):
873         def __init__(self, *args, **kw):
874             apply(_FSBase.__init__, (self,)+args, kw)
875             SCons.Memoize.Memoizer.__init__(self)
876
877
878 #class RemoteFS:
879 #    # Skeleton for the obvious methods we might need from the
880 #    # abstraction layer for a remote filesystem.
881 #    def upload(self, local_src, remote_dst):
882 #        pass
883 #    def download(self, remote_src, local_dst):
884 #        pass
885
886
887 class FS(LocalFS):
888
889     def __init__(self, path = None):
890         """Initialize the Node.FS subsystem.
891
892         The supplied path is the top of the source tree, where we
893         expect to find the top-level build file.  If no path is
894         supplied, the current directory is the default.
895
896         The path argument must be a valid absolute path.
897         """
898         if __debug__: logInstanceCreation(self, 'Node.FS')
899
900         self.Root = {}
901         self.SConstruct_dir = None
902         self.CachePath = None
903         self.cache_force = None
904         self.cache_show = None
905         self.max_drift = default_max_drift
906
907         self.Top = None
908         if path is None:
909             self.pathTop = os.getcwd()
910         else:
911             self.pathTop = path
912         self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
913
914         self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
915         self.Top.path = '.'
916         self.Top.tpath = '.'
917         self._cwd = self.Top
918
919     def clear_cache(self):
920         "__cache_reset__"
921         pass
922     
923     def set_SConstruct_dir(self, dir):
924         self.SConstruct_dir = dir
925
926     def get_max_drift(self):
927         return self.max_drift
928
929     def set_max_drift(self, max_drift):
930         self.max_drift = max_drift
931
932     def getcwd(self):
933         return self._cwd
934
935     def __checkClass(self, node, klass):
936         if isinstance(node, klass) or klass == Entry:
937             return node
938         if node.__class__ == Entry:
939             node.__class__ = klass
940             node._morph()
941             return node
942         raise TypeError, "Tried to lookup %s '%s' as a %s." % \
943               (node.__class__.__name__, node.path, klass.__name__)
944         
945     def _doLookup(self, fsclass, name, directory = None, create = 1):
946         """This method differs from the File and Dir factory methods in
947         one important way: the meaning of the directory parameter.
948         In this method, if directory is None or not supplied, the supplied
949         name is expected to be an absolute path.  If you try to look up a
950         relative path with directory=None, then an AssertionError will be
951         raised.
952         __cacheable__"""
953
954         if not name:
955             # This is a stupid hack to compensate for the fact that the
956             # POSIX and Windows versions of os.path.normpath() behave
957             # differently in older versions of Python.  In particular,
958             # in POSIX:
959             #   os.path.normpath('./') == '.'
960             # in Windows:
961             #   os.path.normpath('./') == ''
962             #   os.path.normpath('.\\') == ''
963             #
964             # This is a definite bug in the Python library, but we have
965             # to live with it.
966             name = '.'
967         path_orig = string.split(name, os.sep)
968         path_norm = string.split(_my_normcase(name), os.sep)
969
970         first_orig = path_orig.pop(0)   # strip first element
971         first_norm = path_norm.pop(0)   # strip first element
972
973         drive, path_first = os.path.splitdrive(first_orig)
974         if path_first:
975             path_orig = [ path_first, ] + path_orig
976             path_norm = [ _my_normcase(path_first), ] + path_norm
977         else:
978             # Absolute path
979             drive = _my_normcase(drive)
980             try:
981                 directory = self.Root[drive]
982             except KeyError:
983                 if not create:
984                     raise SCons.Errors.UserError
985                 directory = RootDir(drive, self)
986                 self.Root[drive] = directory
987                 if not drive:
988                     self.Root[self.defaultDrive] = directory
989                 elif drive == self.defaultDrive:
990                     self.Root[''] = directory
991
992         if not path_orig:
993             return directory
994
995         last_orig = path_orig.pop()     # strip last element
996         last_norm = path_norm.pop()     # strip last element
997             
998         # Lookup the directory
999         for orig, norm in map(None, path_orig, path_norm):
1000             try:
1001                 entries = directory.entries
1002             except AttributeError:
1003                 # We tried to look up the entry in either an Entry or
1004                 # a File.  Give whatever it is a chance to do what's
1005                 # appropriate: morph into a Dir or raise an exception.
1006                 directory.must_be_a_Dir()
1007                 entries = directory.entries
1008             try:
1009                 directory = entries[norm]
1010             except KeyError:
1011                 if not create:
1012                     raise SCons.Errors.UserError
1013
1014                 d = Dir(orig, directory, self)
1015
1016                 # Check the file system (or not, as configured) to make
1017                 # sure there isn't already a file there.
1018                 d.diskcheck_match()
1019
1020                 directory.entries[norm] = d
1021                 directory.add_wkid(d)
1022                 directory = d
1023
1024         directory.must_be_a_Dir()
1025
1026         try:
1027             e = directory.entries[last_norm]
1028         except KeyError:
1029             if not create:
1030                 raise SCons.Errors.UserError
1031
1032             result = fsclass(last_orig, directory, self)
1033
1034             # Check the file system (or not, as configured) to make
1035             # sure there isn't already a directory at the path on
1036             # disk where we just created a File node, and vice versa.
1037             result.diskcheck_match()
1038
1039             directory.entries[last_norm] = result 
1040             directory.add_wkid(result)
1041         else:
1042             result = self.__checkClass(e, fsclass)
1043         return result 
1044
1045     def _transformPath(self, name, directory):
1046         """Take care of setting up the correct top-level directory,
1047         usually in preparation for a call to doLookup().
1048
1049         If the path name is prepended with a '#', then it is unconditionally
1050         interpreted as relative to the top-level directory of this FS.
1051
1052         If directory is None, and name is a relative path,
1053         then the same applies.
1054         """
1055         if not SCons.Util.is_String(name):
1056             # This handles cases where the object is a Proxy wrapping
1057             # a Node.FS.File object (e.g.).  It would be good to handle
1058             # this more directly some day by having the callers of this
1059             # function recognize that a Proxy can be treated like the
1060             # underlying object (that is, get rid of the isinstance()
1061             # calls that explicitly look for a Node.FS.Base object).
1062             name = str(name)
1063         if name and name[0] == '#':
1064             directory = self.Top
1065             name = name[1:]
1066             if name and (name[0] == os.sep or name[0] == '/'):
1067                 # Correct such that '#/foo' is equivalent
1068                 # to '#foo'.
1069                 name = name[1:]
1070             name = os.path.join('.', os.path.normpath(name))
1071         elif not directory:
1072             directory = self._cwd
1073         return (os.path.normpath(name), directory)
1074
1075     def chdir(self, dir, change_os_dir=0):
1076         """Change the current working directory for lookups.
1077         If change_os_dir is true, we will also change the "real" cwd
1078         to match.
1079         """
1080         curr=self._cwd
1081         try:
1082             if not dir is None:
1083                 self._cwd = dir
1084                 if change_os_dir:
1085                     os.chdir(dir.abspath)
1086         except OSError:
1087             self._cwd = curr
1088             raise
1089
1090     def Entry(self, name, directory = None, create = 1, klass=None):
1091         """Lookup or create a generic Entry node with the specified name.
1092         If the name is a relative path (begins with ./, ../, or a file
1093         name), then it is looked up relative to the supplied directory
1094         node, or to the top level directory of the FS (supplied at
1095         construction time) if no directory is supplied.
1096         """
1097
1098         if not klass:
1099             klass = Entry
1100
1101         if isinstance(name, Base):
1102             return self.__checkClass(name, klass)
1103         else:
1104             if directory and not isinstance(directory, Dir):
1105                 directory = self.Dir(directory)
1106             name, directory = self._transformPath(name, directory)
1107             return self._doLookup(klass, name, directory, create)
1108     
1109     def File(self, name, directory = None, create = 1):
1110         """Lookup or create a File node with the specified name.  If
1111         the name is a relative path (begins with ./, ../, or a file name),
1112         then it is looked up relative to the supplied directory node,
1113         or to the top level directory of the FS (supplied at construction
1114         time) if no directory is supplied.
1115
1116         This method will raise TypeError if a directory is found at the
1117         specified path.
1118         """
1119
1120         return self.Entry(name, directory, create, File)
1121     
1122     def Dir(self, name, directory = None, create = 1):
1123         """Lookup or create a Dir node with the specified name.  If
1124         the name is a relative path (begins with ./, ../, or a file name),
1125         then it is looked up relative to the supplied directory node,
1126         or to the top level directory of the FS (supplied at construction
1127         time) if no directory is supplied.
1128
1129         This method will raise TypeError if a normal file is found at the
1130         specified path.
1131         """
1132
1133         return self.Entry(name, directory, create, Dir)
1134     
1135     def BuildDir(self, build_dir, src_dir, duplicate=1):
1136         """Link the supplied build directory to the source directory
1137         for purposes of building files."""
1138         
1139         if not isinstance(src_dir, SCons.Node.Node):
1140             src_dir = self.Dir(src_dir)
1141         if not isinstance(build_dir, SCons.Node.Node):
1142             build_dir = self.Dir(build_dir)
1143         if src_dir.is_under(build_dir):
1144             raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1145         if build_dir.srcdir:
1146             if build_dir.srcdir == src_dir:
1147                 return # We already did this.
1148             raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1149         build_dir.link(src_dir, duplicate)
1150
1151     def Repository(self, *dirs):
1152         """Specify Repository directories to search."""
1153         for d in dirs:
1154             if not isinstance(d, SCons.Node.Node):
1155                 d = self.Dir(d)
1156             self.Top.addRepository(d)
1157
1158     def Rfindalldirs(self, pathlist, cwd):
1159         """__cacheable__"""
1160         if SCons.Util.is_String(pathlist):
1161             pathlist = string.split(pathlist, os.pathsep)
1162         if not SCons.Util.is_List(pathlist):
1163             pathlist = [pathlist]
1164         result = []
1165         for path in filter(None, pathlist):
1166             if isinstance(path, SCons.Node.Node):
1167                 result.append(path)
1168                 continue
1169             path, dir = self._transformPath(path, cwd)
1170             dir = dir.Dir(path)
1171             result.extend(dir.get_all_rdirs())
1172         return result
1173
1174     def CacheDebugWrite(self, fmt, target, cachefile):
1175         self.CacheDebugFP.write(fmt % (target, os.path.split(cachefile)[1]))
1176
1177     def CacheDebugQuiet(self, fmt, target, cachefile):
1178         pass
1179
1180     CacheDebug = CacheDebugQuiet
1181
1182     def CacheDebugEnable(self, file):
1183         if file == '-':
1184             self.CacheDebugFP = sys.stdout
1185         else:
1186             self.CacheDebugFP = open(file, 'w')
1187         self.CacheDebug = self.CacheDebugWrite
1188
1189     def CacheDir(self, path):
1190         self.CachePath = path
1191
1192     def build_dir_target_climb(self, orig, dir, tail):
1193         """Create targets in corresponding build directories
1194
1195         Climb the directory tree, and look up path names
1196         relative to any linked build directories we find.
1197         __cacheable__
1198         """
1199         targets = []
1200         message = None
1201         fmt = "building associated BuildDir targets: %s"
1202         start_dir = dir
1203         while dir:
1204             for bd in dir.build_dirs:
1205                 if start_dir.is_under(bd):
1206                     # If already in the build-dir location, don't reflect
1207                     return [orig], fmt % str(orig)
1208                 p = apply(os.path.join, [bd.path] + tail)
1209                 targets.append(self.Entry(p))
1210             tail = [dir.name] + tail
1211             dir = dir.up()
1212         if targets:
1213             message = fmt % string.join(map(str, targets))
1214         return targets, message
1215
1216 class DirNodeInfo(SCons.Node.NodeInfoBase):
1217     pass
1218
1219 class DirBuildInfo(SCons.Node.BuildInfoBase):
1220     pass
1221
1222 class Dir(Base):
1223     """A class for directories in a file system.
1224     """
1225
1226     NodeInfo = DirNodeInfo
1227     BuildInfo = DirBuildInfo
1228
1229     def __init__(self, name, directory, fs):
1230         if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1231         Base.__init__(self, name, directory, fs)
1232         self._morph()
1233
1234     def _morph(self):
1235         """Turn a file system Node (either a freshly initialized directory
1236         object or a separate Entry object) into a proper directory object.
1237
1238         Set up this directory's entries and hook it into the file
1239         system tree.  Specify that directories (this Node) don't use
1240         signatures for calculating whether they're current.
1241         __cache_reset__"""
1242
1243         self.repositories = []
1244         self.srcdir = None
1245
1246         self.entries = {}
1247         self.entries['.'] = self
1248         self.entries['..'] = self.dir
1249         self.cwd = self
1250         self.searched = 0
1251         self._sconsign = None
1252         self.build_dirs = []
1253
1254         # Don't just reset the executor, replace its action list,
1255         # because it might have some pre-or post-actions that need to
1256         # be preserved.
1257         self.builder = get_MkdirBuilder()
1258         self.get_executor().set_action_list(self.builder.action)
1259
1260     def diskcheck_match(self):
1261         diskcheck_match(self, self.fs.isfile,
1262                            "File %s found where directory expected.")
1263
1264     def __clearRepositoryCache(self, duplicate=None):
1265         """Called when we change the repository(ies) for a directory.
1266         This clears any cached information that is invalidated by changing
1267         the repository."""
1268
1269         for node in self.entries.values():
1270             if node != self.dir:
1271                 if node != self and isinstance(node, Dir):
1272                     node.__clearRepositoryCache(duplicate)
1273                 else:
1274                     node.clear()
1275                     try:
1276                         del node._srcreps
1277                     except AttributeError:
1278                         pass
1279                     if duplicate != None:
1280                         node.duplicate=duplicate
1281     
1282     def __resetDuplicate(self, node):
1283         if node != self:
1284             node.duplicate = node.get_dir().duplicate
1285
1286     def Entry(self, name):
1287         """Create an entry node named 'name' relative to this directory."""
1288         return self.fs.Entry(name, self)
1289
1290     def Dir(self, name):
1291         """Create a directory node named 'name' relative to this directory."""
1292         return self.fs.Dir(name, self)
1293
1294     def File(self, name):
1295         """Create a file node named 'name' relative to this directory."""
1296         return self.fs.File(name, self)
1297
1298     def link(self, srcdir, duplicate):
1299         """Set this directory as the build directory for the
1300         supplied source directory."""
1301         self.srcdir = srcdir
1302         self.duplicate = duplicate
1303         self.__clearRepositoryCache(duplicate)
1304         srcdir.build_dirs.append(self)
1305
1306     def getRepositories(self):
1307         """Returns a list of repositories for this directory.
1308         __cacheable__"""
1309         if self.srcdir and not self.duplicate:
1310             return self.srcdir.get_all_rdirs() + self.repositories
1311         return self.repositories
1312
1313     def get_all_rdirs(self):
1314         """__cacheable__"""
1315         result = [self]
1316         fname = '.'
1317         dir = self
1318         while dir:
1319             for rep in dir.getRepositories():
1320                 result.append(rep.Dir(fname))
1321             fname = dir.name + os.sep + fname
1322             dir = dir.up()
1323         return result
1324
1325     def addRepository(self, dir):
1326         if dir != self and not dir in self.repositories:
1327             self.repositories.append(dir)
1328             dir.tpath = '.'
1329             self.__clearRepositoryCache()
1330
1331     def up(self):
1332         return self.entries['..']
1333
1334     def rel_path(self, other):
1335         """Return a path to "other" relative to this directory.
1336         __cacheable__"""
1337         if isinstance(other, Dir):
1338             name = []
1339         else:
1340             try:
1341                 name = [other.name]
1342                 other = other.dir
1343             except AttributeError:
1344                 return str(other)
1345         if self is other:
1346             return name and name[0] or '.'
1347         i = 0
1348         for x, y in map(None, self.path_elements, other.path_elements):
1349             if not x is y:
1350                 break
1351             i = i + 1
1352         path_elems = ['..']*(len(self.path_elements)-i) \
1353                    + map(lambda n: n.name, other.path_elements[i:]) \
1354                    + name
1355              
1356         return string.join(path_elems, os.sep)
1357
1358     def get_env_scanner(self, env, kw={}):
1359         return SCons.Defaults.DirEntryScanner
1360
1361     def get_target_scanner(self):
1362         return SCons.Defaults.DirEntryScanner
1363
1364     def get_found_includes(self, env, scanner, path):
1365         """Return the included implicit dependencies in this file.
1366         Cache results so we only scan the file once per path
1367         regardless of how many times this information is requested.
1368         __cacheable__"""
1369         if not scanner:
1370             return []
1371         # Clear cached info for this Dir.  If we already visited this
1372         # directory on our walk down the tree (because we didn't know at
1373         # that point it was being used as the source for another Node)
1374         # then we may have calculated build signature before realizing
1375         # we had to scan the disk.  Now that we have to, though, we need
1376         # to invalidate the old calculated signature so that any node
1377         # dependent on our directory structure gets one that includes
1378         # info about everything on disk.
1379         self.clear()
1380         return scanner(self, env, path)
1381
1382     def build(self, **kw):
1383         """A null "builder" for directories."""
1384         global MkdirBuilder
1385         if not self.builder is MkdirBuilder:
1386             apply(SCons.Node.Node.build, [self,], kw)
1387
1388     def _create(self):
1389         """Create this directory, silently and without worrying about
1390         whether the builder is the default or not."""
1391         listDirs = []
1392         parent = self
1393         while parent:
1394             if parent.exists():
1395                 break
1396             listDirs.append(parent)
1397             p = parent.up()
1398             if p is None:
1399                 raise SCons.Errors.StopError, parent.path
1400             parent = p
1401         listDirs.reverse()
1402         for dirnode in listDirs:
1403             try:
1404                 # Don't call dirnode.build(), call the base Node method
1405                 # directly because we definitely *must* create this
1406                 # directory.  The dirnode.build() method will suppress
1407                 # the build if it's the default builder.
1408                 SCons.Node.Node.build(dirnode)
1409                 dirnode.get_executor().nullify()
1410                 # The build() action may or may not have actually
1411                 # created the directory, depending on whether the -n
1412                 # option was used or not.  Delete the _exists and
1413                 # _rexists attributes so they can be reevaluated.
1414                 dirnode.clear()
1415             except OSError:
1416                 pass
1417
1418     def multiple_side_effect_has_builder(self):
1419         global MkdirBuilder
1420         return not self.builder is MkdirBuilder and self.has_builder()
1421
1422     def alter_targets(self):
1423         """Return any corresponding targets in a build directory.
1424         """
1425         return self.fs.build_dir_target_climb(self, self, [])
1426
1427     def scanner_key(self):
1428         """A directory does not get scanned."""
1429         return None
1430
1431     def get_contents(self):
1432         """Return aggregate contents of all our children."""
1433         contents = map(lambda n: n.get_contents(), self.children())
1434         return  string.join(contents, '')
1435
1436     def prepare(self):
1437         pass
1438
1439     def do_duplicate(self, src):
1440         pass
1441
1442     def current(self, calc=None):
1443         """If any child is not up-to-date, then this directory isn't,
1444         either."""
1445         if not self.builder is MkdirBuilder and not self.exists():
1446             return 0
1447         up_to_date = SCons.Node.up_to_date
1448         for kid in self.children():
1449             if kid.get_state() > up_to_date:
1450                 return 0
1451         return 1
1452
1453     def rdir(self):
1454         "__cacheable__"
1455         if not self.exists():
1456             norm_name = _my_normcase(self.name)
1457             for dir in self.dir.get_all_rdirs():
1458                 try: node = dir.entries[norm_name]
1459                 except KeyError: node = dir.dir_on_disk(self.name)
1460                 if node and node.exists() and \
1461                     (isinstance(dir, Dir) or isinstance(dir, Entry)):
1462                         return node
1463         return self
1464
1465     def sconsign(self):
1466         """Return the .sconsign file info for this directory,
1467         creating it first if necessary."""
1468         if not self._sconsign:
1469             import SCons.SConsign
1470             self._sconsign = SCons.SConsign.ForDirectory(self)
1471         return self._sconsign
1472
1473     def srcnode(self):
1474         """Dir has a special need for srcnode()...if we
1475         have a srcdir attribute set, then that *is* our srcnode."""
1476         if self.srcdir:
1477             return self.srcdir
1478         return Base.srcnode(self)
1479
1480     def get_timestamp(self):
1481         """Return the latest timestamp from among our children"""
1482         stamp = 0
1483         for kid in self.children():
1484             if kid.get_timestamp() > stamp:
1485                 stamp = kid.get_timestamp()
1486         return stamp
1487
1488     def entry_abspath(self, name):
1489         return self.abspath + os.sep + name
1490
1491     def entry_path(self, name):
1492         return self.path + os.sep + name
1493
1494     def entry_tpath(self, name):
1495         return self.tpath + os.sep + name
1496
1497     def must_be_a_Dir(self):
1498         """Called to make sure a Node is a Dir.  Since we're already
1499         one, this is a no-op for us."""
1500         return self
1501
1502     def entry_exists_on_disk(self, name):
1503         """__cacheable__"""
1504         try:
1505             d = self.on_disk_entries
1506         except AttributeError:
1507             d = {}
1508             try:
1509                 entries = os.listdir(self.abspath)
1510             except OSError:
1511                 pass
1512             else:
1513                 for entry in map(_my_normcase, entries):
1514                     d[entry] = 1
1515             self.on_disk_entries = d
1516         return d.has_key(_my_normcase(name))
1517
1518     def srcdir_list(self):
1519         """__cacheable__"""
1520         result = []
1521
1522         dirname = '.'
1523         dir = self
1524         while dir:
1525             if dir.srcdir:
1526                 d = dir.srcdir.Dir(dirname)
1527                 if d.is_under(dir):
1528                     # Shouldn't source from something in the build path:
1529                     # build_dir is probably under src_dir, in which case
1530                     # we are reflecting.
1531                     break
1532                 result.append(d)
1533             dirname = dir.name + os.sep + dirname
1534             dir = dir.up()
1535
1536         return result
1537
1538     def srcdir_duplicate(self, name):
1539         for dir in self.srcdir_list():
1540             if dir.entry_exists_on_disk(name):
1541                 srcnode = dir.File(name)
1542                 if self.duplicate:
1543                     node = self.File(name)
1544                     node.do_duplicate(srcnode)
1545                     return node
1546                 else:
1547                     return srcnode
1548         return None
1549
1550     def srcdir_find_file(self, filename):
1551         """__cacheable__"""
1552         def func(node):
1553             if (isinstance(node, File) or isinstance(node, Entry)) and \
1554                (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1555                     return node
1556             return None
1557
1558         norm_name = _my_normcase(filename)
1559
1560         for rdir in self.get_all_rdirs():
1561             try: node = rdir.entries[norm_name]
1562             except KeyError: node = rdir.file_on_disk(filename)
1563             else: node = func(node)
1564             if node:
1565                 return node, self
1566
1567         for srcdir in self.srcdir_list():
1568             for rdir in srcdir.get_all_rdirs():
1569                 try: node = rdir.entries[norm_name]
1570                 except KeyError: node = rdir.file_on_disk(filename)
1571                 else: node = func(node)
1572                 if node:
1573                     return File(filename, self, self.fs), srcdir
1574
1575         return None, None
1576
1577     def dir_on_disk(self, name):
1578         if self.entry_exists_on_disk(name):
1579             try: return self.Dir(name)
1580             except TypeError: pass
1581         return None
1582
1583     def file_on_disk(self, name):
1584         if self.entry_exists_on_disk(name) or \
1585            diskcheck_rcs(self, name) or \
1586            diskcheck_sccs(self, name):
1587             try: return self.File(name)
1588             except TypeError: pass
1589         return self.srcdir_duplicate(name)
1590
1591 class RootDir(Dir):
1592     """A class for the root directory of a file system.
1593
1594     This is the same as a Dir class, except that the path separator
1595     ('/' or '\\') is actually part of the name, so we don't need to
1596     add a separator when creating the path names of entries within
1597     this directory.
1598     """
1599     def __init__(self, name, fs):
1600         if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1601         # We're going to be our own parent directory (".." entry and .dir
1602         # attribute) so we have to set up some values so Base.__init__()
1603         # won't gag won't it calls some of our methods.
1604         self.abspath = ''
1605         self.path = ''
1606         self.tpath = ''
1607         self.path_elements = []
1608         self.duplicate = 0
1609         Base.__init__(self, name, self, fs)
1610
1611         # Now set our paths to what we really want them to be: the
1612         # initial drive letter (the name) plus the directory separator.
1613         self.abspath = name + os.sep
1614         self.path = name + os.sep
1615         self.tpath = name + os.sep
1616         self._morph()
1617
1618     def __str__(self):
1619         return self.abspath
1620
1621     def entry_abspath(self, name):
1622         return self.abspath + name
1623
1624     def entry_path(self, name):
1625         return self.path + name
1626
1627     def entry_tpath(self, name):
1628         return self.tpath + name
1629
1630     def is_under(self, dir):
1631         if self is dir:
1632             return 1
1633         else:
1634             return 0
1635
1636     def up(self):
1637         return None
1638
1639     def get_dir(self):
1640         return None
1641
1642     def src_builder(self):
1643         return _null
1644
1645 class FileNodeInfo(SCons.Node.NodeInfoBase):
1646     def __init__(self, node):
1647         SCons.Node.NodeInfoBase.__init__(self, node)
1648         self.update(node)
1649     def __cmp__(self, other):
1650         try: return cmp(self.bsig, other.bsig)
1651         except AttributeError: return 1
1652     def update(self, node):
1653         self.timestamp = node.get_timestamp()
1654         self.size = node.getsize()
1655
1656 class FileBuildInfo(SCons.Node.BuildInfoBase):
1657     def __init__(self, node):
1658         SCons.Node.BuildInfoBase.__init__(self, node)
1659         self.node = node
1660     def convert_to_sconsign(self):
1661         """Convert this FileBuildInfo object for writing to a .sconsign file
1662
1663         We hung onto the node that we refer to so that we can translate
1664         the lists of bsources, bdepends and bimplicit Nodes into strings
1665         relative to the node, but we don't want to write out that Node
1666         itself to the .sconsign file, so we delete the attribute in
1667         preparation.
1668         """
1669         rel_path = self.node.rel_path
1670         delattr(self, 'node')
1671         for attr in ['bsources', 'bdepends', 'bimplicit']:
1672             try:
1673                 val = getattr(self, attr)
1674             except AttributeError:
1675                 pass
1676             else:
1677                 setattr(self, attr, map(rel_path, val))
1678     def convert_from_sconsign(self, dir, name):
1679         """Convert a newly-read FileBuildInfo object for in-SCons use
1680
1681         An on-disk BuildInfo comes without a reference to the node for
1682         which it's intended, so we have to convert the arguments and add
1683         back a self.node attribute.  We don't worry here about converting
1684         the bsources, bdepends and bimplicit lists from strings to Nodes
1685         because they're not used in the normal case of just deciding
1686         whether or not to rebuild things.
1687         """
1688         self.node = dir.Entry(name)
1689     def prepare_dependencies(self):
1690         """Prepare a FileBuildInfo object for explaining what changed
1691
1692         The bsources, bdepends and bimplicit lists have all been stored
1693         on disk as paths relative to the Node for which they're stored
1694         as dependency info.  Convert the strings to actual Nodes (for
1695         use by the --debug=explain code and --implicit-cache).
1696         """
1697         Entry_func = self.node.dir.Entry
1698         for attr in ['bsources', 'bdepends', 'bimplicit']:
1699             try:
1700                 val = getattr(self, attr)
1701             except AttributeError:
1702                 pass
1703             else:
1704                 setattr(self, attr, map(Entry_func, val))
1705     def format(self):
1706         result = [ self.ninfo.format() ]
1707         bkids = self.bsources + self.bdepends + self.bimplicit
1708         bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
1709         for i in xrange(len(bkids)):
1710             result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
1711         return string.join(result, '\n')
1712
1713 class NodeInfo(FileNodeInfo):
1714     pass
1715
1716 class BuildInfo(FileBuildInfo):
1717     pass
1718
1719 class File(Base):
1720     """A class for files in a file system.
1721     """
1722
1723     NodeInfo = FileNodeInfo
1724     BuildInfo = FileBuildInfo
1725
1726     def diskcheck_match(self):
1727         diskcheck_match(self, self.fs.isdir,
1728                            "Directory %s found where file expected.")
1729
1730     def __init__(self, name, directory, fs):
1731         if __debug__: logInstanceCreation(self, 'Node.FS.File')
1732         Base.__init__(self, name, directory, fs)
1733         self._morph()
1734
1735     def Entry(self, name):
1736         """Create an entry node named 'name' relative to
1737         the SConscript directory of this file."""
1738         return self.fs.Entry(name, self.cwd)
1739
1740     def Dir(self, name):
1741         """Create a directory node named 'name' relative to
1742         the SConscript directory of this file."""
1743         return self.fs.Dir(name, self.cwd)
1744
1745     def Dirs(self, pathlist):
1746         """Create a list of directories relative to the SConscript
1747         directory of this file."""
1748         return map(lambda p, s=self: s.Dir(p), pathlist)
1749
1750     def File(self, name):
1751         """Create a file node named 'name' relative to
1752         the SConscript directory of this file."""
1753         return self.fs.File(name, self.cwd)
1754
1755     #def generate_build_dict(self):
1756     #    """Return an appropriate dictionary of values for building
1757     #    this File."""
1758     #    return {'Dir' : self.Dir,
1759     #            'File' : self.File,
1760     #            'RDirs' : self.RDirs}
1761
1762     def _morph(self):
1763         """Turn a file system node into a File object.  __cache_reset__"""
1764         self.scanner_paths = {}
1765         if not hasattr(self, '_local'):
1766             self._local = 0
1767
1768     def scanner_key(self):
1769         return self.get_suffix()
1770
1771     def get_contents(self):
1772         if not self.rexists():
1773             return ''
1774         return open(self.rfile().abspath, "rb").read()
1775
1776     def get_timestamp(self):
1777         if self.rexists():
1778             return self.rfile().getmtime()
1779         else:
1780             return 0
1781
1782     def store_info(self, obj):
1783         # Merge our build information into the already-stored entry.
1784         # This accomodates "chained builds" where a file that's a target
1785         # in one build (SConstruct file) is a source in a different build.
1786         # See test/chained-build.py for the use case.
1787         entry = self.get_stored_info()
1788         entry.merge(obj)
1789         self.dir.sconsign().set_entry(self.name, entry)
1790
1791     def get_stored_info(self):
1792         "__cacheable__"
1793         try:
1794             stored = self.dir.sconsign().get_entry(self.name)
1795         except (KeyError, OSError):
1796             return self.new_binfo()
1797         else:
1798             if not hasattr(stored, 'ninfo'):
1799                 # Transition:  The .sconsign file entry has no NodeInfo
1800                 # object, which means it's a slightly older BuildInfo.
1801                 # Copy over the relevant attributes.
1802                 ninfo = stored.ninfo = self.new_ninfo()
1803                 for attr in ninfo.__dict__.keys():
1804                     try:
1805                         setattr(ninfo, attr, getattr(stored, attr))
1806                     except AttributeError:
1807                         pass
1808             return stored
1809
1810     def get_stored_implicit(self):
1811         binfo = self.get_stored_info()
1812         binfo.prepare_dependencies()
1813         try: return binfo.bimplicit
1814         except AttributeError: return None
1815
1816     def rel_path(self, other):
1817         return self.dir.rel_path(other)
1818
1819     def get_found_includes(self, env, scanner, path):
1820         """Return the included implicit dependencies in this file.
1821         Cache results so we only scan the file once per path
1822         regardless of how many times this information is requested.
1823         __cacheable__"""
1824         if not scanner:
1825             return []
1826         return scanner(self, env, path)
1827
1828     def _createDir(self):
1829         # ensure that the directories for this node are
1830         # created.
1831         self.dir._create()
1832
1833     def retrieve_from_cache(self):
1834         """Try to retrieve the node's content from a cache
1835
1836         This method is called from multiple threads in a parallel build,
1837         so only do thread safe stuff here. Do thread unsafe stuff in
1838         built().
1839
1840         Note that there's a special trick here with the execute flag
1841         (one that's not normally done for other actions).  Basically
1842         if the user requested a noexec (-n) build, then
1843         SCons.Action.execute_actions is set to 0 and when any action
1844         is called, it does its showing but then just returns zero
1845         instead of actually calling the action execution operation.
1846         The problem for caching is that if the file does NOT exist in
1847         cache then the CacheRetrieveString won't return anything to
1848         show for the task, but the Action.__call__ won't call
1849         CacheRetrieveFunc; instead it just returns zero, which makes
1850         the code below think that the file *was* successfully
1851         retrieved from the cache, therefore it doesn't do any
1852         subsequent building.  However, the CacheRetrieveString didn't
1853         print anything because it didn't actually exist in the cache,
1854         and no more build actions will be performed, so the user just
1855         sees nothing.  The fix is to tell Action.__call__ to always
1856         execute the CacheRetrieveFunc and then have the latter
1857         explicitly check SCons.Action.execute_actions itself.
1858
1859         Returns true iff the node was successfully retrieved.
1860         """
1861         b = self.is_derived()
1862         if not b and not self.has_src_builder():
1863             return None
1864         if b and self.fs.CachePath:
1865             if self.fs.cache_show:
1866                 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1867                     self.build(presub=0, execute=0)
1868                     self.set_state(SCons.Node.executed)
1869                     return 1
1870             elif CacheRetrieve(self, [], None, execute=1) == 0:
1871                 self.set_state(SCons.Node.executed)
1872                 return 1
1873         return None
1874
1875
1876     def built(self):
1877         """Called just after this node is successfully built.
1878         __cache_reset__"""
1879         # Push this file out to cache before the superclass Node.built()
1880         # method has a chance to clear the build signature, which it
1881         # will do if this file has a source scanner.
1882         if self.fs.CachePath and self.exists():
1883             CachePush(self, [], None)
1884         self.fs.clear_cache()
1885         SCons.Node.Node.built(self)
1886
1887     def visited(self):
1888         if self.fs.CachePath and self.fs.cache_force and self.exists():
1889             CachePush(self, None, None)
1890
1891     def has_src_builder(self):
1892         """Return whether this Node has a source builder or not.
1893
1894         If this Node doesn't have an explicit source code builder, this
1895         is where we figure out, on the fly, if there's a transparent
1896         source code builder for it.
1897
1898         Note that if we found a source builder, we also set the
1899         self.builder attribute, so that all of the methods that actually
1900         *build* this file don't have to do anything different.
1901         """
1902         try:
1903             scb = self.sbuilder
1904         except AttributeError:
1905             if self.rexists():
1906                 scb = None
1907             else:
1908                 scb = self.dir.src_builder()
1909                 if scb is _null:
1910                     if diskcheck_sccs(self.dir, self.name):
1911                         scb = get_DefaultSCCSBuilder()
1912                     elif diskcheck_rcs(self.dir, self.name):
1913                         scb = get_DefaultRCSBuilder()
1914                     else:
1915                         scb = None
1916                 if scb is not None:
1917                     self.builder_set(scb)
1918             self.sbuilder = scb
1919         return not scb is None
1920
1921     def alter_targets(self):
1922         """Return any corresponding targets in a build directory.
1923         """
1924         if self.is_derived():
1925             return [], None
1926         return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1927
1928     def is_pseudo_derived(self):
1929         "__cacheable__"
1930         return self.has_src_builder()
1931
1932     def _rmv_existing(self):
1933         '__cache_reset__'
1934         Unlink(self, [], None)
1935         
1936     def prepare(self):
1937         """Prepare for this file to be created."""
1938         SCons.Node.Node.prepare(self)
1939
1940         if self.get_state() != SCons.Node.up_to_date:
1941             if self.exists():
1942                 if self.is_derived() and not self.precious:
1943                     self._rmv_existing()
1944             else:
1945                 try:
1946                     self._createDir()
1947                 except SCons.Errors.StopError, drive:
1948                     desc = "No drive `%s' for target `%s'." % (drive, self)
1949                     raise SCons.Errors.StopError, desc
1950
1951     def remove(self):
1952         """Remove this file."""
1953         if self.exists() or self.islink():
1954             self.fs.unlink(self.path)
1955             return 1
1956         return None
1957
1958     def do_duplicate(self, src):
1959         self._createDir()
1960         try:
1961             Unlink(self, None, None)
1962         except SCons.Errors.BuildError:
1963             pass
1964         try:
1965             Link(self, src, None)
1966         except SCons.Errors.BuildError, e:
1967             desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1968             raise SCons.Errors.StopError, desc
1969         self.linked = 1
1970         # The Link() action may or may not have actually
1971         # created the file, depending on whether the -n
1972         # option was used or not.  Delete the _exists and
1973         # _rexists attributes so they can be reevaluated.
1974         self.clear()
1975
1976     def exists(self):
1977         "__cacheable__"
1978         # Duplicate from source path if we are set up to do this.
1979         if self.duplicate and not self.is_derived() and not self.linked:
1980             src = self.srcnode()
1981             if src is self:
1982                 return Base.exists(self)
1983             # At this point, src is meant to be copied in a build directory.
1984             src = src.rfile()
1985             if src.abspath != self.abspath:
1986                 if src.exists():
1987                     self.do_duplicate(src)
1988                     # Can't return 1 here because the duplication might
1989                     # not actually occur if the -n option is being used.
1990                 else:
1991                     # The source file does not exist.  Make sure no old
1992                     # copy remains in the build directory.
1993                     if Base.exists(self) or self.islink():
1994                         self.fs.unlink(self.path)
1995                     # Return None explicitly because the Base.exists() call
1996                     # above will have cached its value if the file existed.
1997                     return None
1998         return Base.exists(self)
1999
2000     #
2001     # SIGNATURE SUBSYSTEM
2002     #
2003
2004     def get_csig(self, calc=None):
2005         """
2006         Generate a node's content signature, the digested signature
2007         of its content.
2008
2009         node - the node
2010         cache - alternate node to use for the signature cache
2011         returns - the content signature
2012         """
2013         try:
2014             return self.binfo.ninfo.csig
2015         except AttributeError:
2016             pass
2017
2018         if calc is None:
2019             calc = self.calculator()
2020
2021         max_drift = self.fs.max_drift
2022         mtime = self.get_timestamp()
2023         use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
2024
2025         csig = None
2026         if use_stored:
2027             old = self.get_stored_info().ninfo
2028             try:
2029                 if old.timestamp and old.csig and old.timestamp == mtime:
2030                     csig = old.csig
2031             except AttributeError:
2032                 pass
2033         if csig is None:
2034             csig = calc.module.signature(self)
2035
2036         binfo = self.get_binfo()
2037         ninfo = binfo.ninfo
2038         ninfo.csig = csig
2039         ninfo.update(self)
2040
2041         if use_stored:
2042             self.store_info(binfo)
2043
2044         return csig
2045
2046     #
2047     #
2048     #
2049
2050     def is_up_to_date(self, node=None, bi=None):
2051         """Returns if the node is up-to-date with respect to stored
2052         BuildInfo.  The default is to compare it against our own
2053         previously stored BuildInfo, but the stored BuildInfo from another
2054         Node (typically one in a Repository) can be used instead."""
2055         if bi is None:
2056             if node is None:
2057                 node = self
2058             bi = node.get_stored_info()
2059         new = self.get_binfo()
2060         return new == bi
2061
2062     def current(self, calc=None):
2063         self.binfo = self.gen_binfo(calc)
2064         return self._cur2()
2065     def _cur2(self):
2066         "__cacheable__"
2067         if self.always_build:
2068             return None
2069         if not self.exists():
2070             # The file doesn't exist locally...
2071             r = self.rfile()
2072             if r != self:
2073                 # ...but there is one in a Repository...
2074                 if self.is_up_to_date(r):
2075                     # ...and it's even up-to-date...
2076                     if self._local:
2077                         # ...and they'd like a local copy.
2078                         LocalCopy(self, r, None)
2079                         self.store_info(self.get_binfo())
2080                     return 1
2081             return None
2082         else:
2083             return self.is_up_to_date()
2084
2085     def rfile(self):
2086         "__cacheable__"
2087         if not self.exists():
2088             norm_name = _my_normcase(self.name)
2089             for dir in self.dir.get_all_rdirs():
2090                 try: node = dir.entries[norm_name]
2091                 except KeyError: node = dir.file_on_disk(self.name)
2092                 if node and node.exists() and \
2093                    (isinstance(node, File) or isinstance(node, Entry) \
2094                     or not node.is_derived()):
2095                         return node
2096         return self
2097
2098     def rstr(self):
2099         return str(self.rfile())
2100
2101     def cachepath(self):
2102         if not self.fs.CachePath:
2103             return None, None
2104         ninfo = self.get_binfo().ninfo
2105         if not hasattr(ninfo, 'bsig'):
2106             raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
2107         elif ninfo.bsig is None:
2108             raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
2109         # Add the path to the cache signature, because multiple
2110         # targets built by the same action will all have the same
2111         # build signature, and we have to differentiate them somehow.
2112         cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
2113         subdir = string.upper(cache_sig[0])
2114         dir = os.path.join(self.fs.CachePath, subdir)
2115         return dir, os.path.join(dir, cache_sig)
2116
2117     def must_be_a_Dir(self):
2118         """Called to make sure a Node is a Dir.  Since we're already a
2119         File, this is a TypeError..."""
2120         raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
2121
2122 default_fs = None
2123
2124 def find_file(filename, paths, verbose=None):
2125     """
2126     find_file(str, [Dir()]) -> [nodes]
2127
2128     filename - a filename to find
2129     paths - a list of directory path *nodes* to search in.  Can be
2130             represented as a list, a tuple, or a callable that is
2131             called with no arguments and returns the list or tuple.
2132
2133     returns - the node created from the found file.
2134
2135     Find a node corresponding to either a derived file or a file
2136     that exists already.
2137
2138     Only the first file found is returned, and none is returned
2139     if no file is found.
2140     __cacheable__
2141     """
2142     if verbose:
2143         if not SCons.Util.is_String(verbose):
2144             verbose = "find_file"
2145         if not callable(verbose):
2146             verbose = '  %s: ' % verbose
2147             verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2148     else:
2149         verbose = lambda x: x
2150
2151     if callable(paths):
2152         paths = paths()
2153
2154     # Give Entries a chance to morph into Dirs.
2155     paths = map(lambda p: p.must_be_a_Dir(), paths)
2156
2157     filedir, filename = os.path.split(filename)
2158     if filedir:
2159         def filedir_lookup(p, fd=filedir):
2160             try:
2161                 return p.Dir(fd)
2162             except TypeError:
2163                 # We tried to look up a Dir, but it seems there's already
2164                 # a File (or something else) there.  No big.
2165                 return None
2166         paths = filter(None, map(filedir_lookup, paths))
2167
2168     for dir in paths:
2169         verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2170         node, d = dir.srcdir_find_file(filename)
2171         if node:
2172             verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2173             return node
2174     return None
2175
2176 def find_files(filenames, paths):
2177     """
2178     find_files([str], [Dir()]) -> [nodes]
2179
2180     filenames - a list of filenames to find
2181     paths - a list of directory path *nodes* to search in
2182
2183     returns - the nodes created from the found files.
2184
2185     Finds nodes corresponding to either derived files or files
2186     that exist already.
2187
2188     Only the first file found is returned for each filename,
2189     and any files that aren't found are ignored.
2190     """
2191     nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2192     return filter(None, nodes)