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