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