Add a --debug=nomemoizer option to disable memoization.
[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 = None
1469     def __cmp__(self, other):
1470         try:
1471             return cmp(self.bsig, other.bsig)
1472         except AttributeError:
1473             return 1
1474
1475 class File(Base):
1476     """A class for files in a file system.
1477     """
1478     def __init__(self, name, directory, fs):
1479         if __debug__: logInstanceCreation(self, 'Node.FS.File')
1480         Base.__init__(self, name, directory, fs)
1481         self._morph()
1482
1483     def Entry(self, name):
1484         """Create an entry node named 'name' relative to
1485         the SConscript directory of this file."""
1486         return self.fs.Entry(name, self.cwd)
1487
1488     def Dir(self, name):
1489         """Create a directory node named 'name' relative to
1490         the SConscript directory of this file."""
1491         return self.fs.Dir(name, self.cwd)
1492
1493     def File(self, name):
1494         """Create a file node named 'name' relative to
1495         the SConscript directory of this file."""
1496         return self.fs.File(name, self.cwd)
1497
1498     def RDirs(self, pathlist):
1499         """Search for a list of directories in the Repository list."""
1500         return self.fs.Rfindalldirs(pathlist, self.cwd)
1501
1502     def _morph(self):
1503         """Turn a file system node into a File object.  __cache_reset__"""
1504         self.scanner_paths = {}
1505         if not hasattr(self, '_local'):
1506             self._local = 0
1507
1508     def disambiguate(self):
1509         return self
1510
1511     def scanner_key(self):
1512         return self.get_suffix()
1513
1514     def get_contents(self):
1515         if not self.rexists():
1516             return ''
1517         return open(self.rfile().abspath, "rb").read()
1518
1519     def get_timestamp(self):
1520         if self.rexists():
1521             return self.rfile().getmtime()
1522         else:
1523             return 0
1524
1525     def store_info(self, obj):
1526         # Merge our build information into the already-stored entry.
1527         # This accomodates "chained builds" where a file that's a target
1528         # in one build (SConstruct file) is a source in a different build.
1529         # See test/chained-build.py for the use case.
1530         entry = self.get_stored_info()
1531         for key, val in obj.__dict__.items():
1532             entry.__dict__[key] = val
1533         self.dir.sconsign().set_entry(self.name, entry)
1534
1535     def get_stored_info(self):
1536         "__cacheable__"
1537         try:
1538             stored = self.dir.sconsign().get_entry(self.name)
1539         except (KeyError, OSError):
1540             return BuildInfo()
1541         else:
1542             if isinstance(stored, BuildInfo):
1543                 return stored
1544             # The stored build information isn't a BuildInfo object.
1545             # This probably means it's an old SConsignEntry from SCons
1546             # 0.95 or before.  The relevant attribute names are the same,
1547             # though, so just copy the attributes over to an object of
1548             # the correct type.
1549             binfo = BuildInfo()
1550             for key, val in stored.__dict__.items():
1551                 setattr(binfo, key, val)
1552             return binfo
1553
1554     def get_stored_implicit(self):
1555         binfo = self.get_stored_info()
1556         try: implicit = binfo.bimplicit
1557         except AttributeError: return None
1558         else: return map(self.dir.Entry, implicit)
1559
1560     def rel_path(self, other):
1561         return self.dir.rel_path(other)
1562
1563     def get_found_includes(self, env, scanner, path):
1564         """Return the included implicit dependencies in this file.
1565         Cache results so we only scan the file once per path
1566         regardless of how many times this information is requested.
1567         __cacheable__"""
1568         if not scanner:
1569             return []
1570         return scanner(self, env, path)
1571
1572     def _createDir(self):
1573         # ensure that the directories for this node are
1574         # created.
1575         self.dir._create()
1576
1577     def retrieve_from_cache(self):
1578         """Try to retrieve the node's content from a cache
1579
1580         This method is called from multiple threads in a parallel build,
1581         so only do thread safe stuff here. Do thread unsafe stuff in
1582         built().
1583
1584         Note that there's a special trick here with the execute flag
1585         (one that's not normally done for other actions).  Basically
1586         if the user requested a noexec (-n) build, then
1587         SCons.Action.execute_actions is set to 0 and when any action
1588         is called, it does its showing but then just returns zero
1589         instead of actually calling the action execution operation.
1590         The problem for caching is that if the file does NOT exist in
1591         cache then the CacheRetrieveString won't return anything to
1592         show for the task, but the Action.__call__ won't call
1593         CacheRetrieveFunc; instead it just returns zero, which makes
1594         the code below think that the file *was* successfully
1595         retrieved from the cache, therefore it doesn't do any
1596         subsequent building.  However, the CacheRetrieveString didn't
1597         print anything because it didn't actually exist in the cache,
1598         and no more build actions will be performed, so the user just
1599         sees nothing.  The fix is to tell Action.__call__ to always
1600         execute the CacheRetrieveFunc and then have the latter
1601         explicitly check SCons.Action.execute_actions itself.
1602
1603         Returns true iff the node was successfully retrieved.
1604         """
1605         b = self.is_derived()
1606         if not b and not self.has_src_builder():
1607             return None
1608         if b and self.fs.CachePath:
1609             if self.fs.cache_show:
1610                 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1611                     self.build(presub=0, execute=0)
1612                     return 1
1613             elif CacheRetrieve(self, [], None, execute=1) == 0:
1614                 return 1
1615         return None
1616
1617     def built(self):
1618         """Called just after this node is successfully built.
1619         __cache_reset__"""
1620         # Push this file out to cache before the superclass Node.built()
1621         # method has a chance to clear the build signature, which it
1622         # will do if this file has a source scanner.
1623         if self.fs.CachePath and self.exists():
1624             CachePush(self, [], None)
1625         self.fs.clear_cache()
1626         SCons.Node.Node.built(self)
1627
1628     def visited(self):
1629         if self.fs.CachePath and self.fs.cache_force and self.exists():
1630             CachePush(self, None, None)
1631
1632     def has_src_builder(self):
1633         """Return whether this Node has a source builder or not.
1634
1635         If this Node doesn't have an explicit source code builder, this
1636         is where we figure out, on the fly, if there's a transparent
1637         source code builder for it.
1638
1639         Note that if we found a source builder, we also set the
1640         self.builder attribute, so that all of the methods that actually
1641         *build* this file don't have to do anything different.
1642         """
1643         try:
1644             scb = self.sbuilder
1645         except AttributeError:
1646             if self.rexists():
1647                 scb = None
1648             else:
1649                 scb = self.dir.src_builder()
1650                 if scb is _null:
1651                     if self.dir.sccs_on_disk(self.name):
1652                         scb = get_DefaultSCCSBuilder()
1653                     elif self.dir.rcs_on_disk(self.name):
1654                         scb = get_DefaultRCSBuilder()
1655                     else:
1656                         scb = None
1657                 if scb is not None:
1658                     self.builder_set(scb)
1659             self.sbuilder = scb
1660         return not scb is None
1661
1662     def alter_targets(self):
1663         """Return any corresponding targets in a build directory.
1664         """
1665         if self.is_derived():
1666             return [], None
1667         return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1668
1669     def is_pseudo_derived(self):
1670         "__cacheable__"
1671         return self.has_src_builder()
1672
1673     def _rmv_existing(self):
1674         '__cache_reset__'
1675         Unlink(self, [], None)
1676         
1677     def prepare(self):
1678         """Prepare for this file to be created."""
1679         SCons.Node.Node.prepare(self)
1680
1681         if self.get_state() != SCons.Node.up_to_date:
1682             if self.exists():
1683                 if self.is_derived() and not self.precious:
1684                     self._rmv_existing()
1685             else:
1686                 try:
1687                     self._createDir()
1688                 except SCons.Errors.StopError, drive:
1689                     desc = "No drive `%s' for target `%s'." % (drive, self)
1690                     raise SCons.Errors.StopError, desc
1691
1692     def remove(self):
1693         """Remove this file."""
1694         if self.exists() or self.islink():
1695             self.fs.unlink(self.path)
1696             return 1
1697         return None
1698
1699     def do_duplicate(self, src):
1700         self._createDir()
1701         try:
1702             Unlink(self, None, None)
1703         except SCons.Errors.BuildError:
1704             pass
1705         try:
1706             Link(self, src, None)
1707         except SCons.Errors.BuildError, e:
1708             desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1709             raise SCons.Errors.StopError, desc
1710         self.linked = 1
1711         # The Link() action may or may not have actually
1712         # created the file, depending on whether the -n
1713         # option was used or not.  Delete the _exists and
1714         # _rexists attributes so they can be reevaluated.
1715         self.clear()
1716
1717     def exists(self):
1718         "__cacheable__"
1719         # Duplicate from source path if we are set up to do this.
1720         if self.duplicate and not self.is_derived() and not self.linked:
1721             src=self.srcnode()
1722             if src is self:
1723                 return Base.exists(self)
1724             src = src.rfile()
1725             if src.abspath != self.abspath and src.exists():
1726                 self.do_duplicate(src)
1727         return Base.exists(self)
1728
1729     def new_binfo(self):
1730         return BuildInfo()
1731
1732     def del_cinfo(self):
1733         try:
1734             del self.binfo.csig
1735         except AttributeError:
1736             pass
1737         try:
1738             del self.binfo.timestamp
1739         except AttributeError:
1740             pass
1741
1742     def calc_csig(self, calc=None):
1743         """
1744         Generate a node's content signature, the digested signature
1745         of its content.
1746
1747         node - the node
1748         cache - alternate node to use for the signature cache
1749         returns - the content signature
1750         """
1751         if calc is None:
1752             calc = self.calculator()
1753
1754         try:
1755             return self.binfo.csig
1756         except AttributeError:
1757             pass
1758         
1759         if calc.max_drift >= 0:
1760             old = self.get_stored_info()
1761         else:
1762             old = BuildInfo()
1763
1764         try:
1765             mtime = self.get_timestamp()
1766         except OSError:
1767             mtime = 0
1768             raise SCons.Errors.UserError, "no such %s" % self
1769
1770         try:
1771             if (old.timestamp and old.csig and old.timestamp == mtime):
1772                 # use the signature stored in the .sconsign file
1773                 csig = old.csig
1774             else:
1775                 csig = calc.module.signature(self)
1776         except AttributeError:
1777             csig = calc.module.signature(self)
1778
1779         if calc.max_drift >= 0 and (time.time() - mtime) > calc.max_drift:
1780             try:
1781                 binfo = self.binfo
1782             except AttributeError:
1783                 binfo = self.binfo = self.new_binfo()
1784             binfo.csig = csig
1785             binfo.timestamp = mtime
1786             self.store_info(binfo)
1787
1788         return csig
1789
1790     def current(self, calc=None):
1791         self.binfo = self.gen_binfo(calc)
1792         return self._cur2()
1793     def _cur2(self):
1794         "__cacheable__"
1795         if self.always_build:
1796             return None
1797         if not self.exists():
1798             # The file doesn't exist locally...
1799             r = self.rfile()
1800             if r != self:
1801                 # ...but there is one in a Repository...
1802                 old = r.get_stored_info()
1803                 if old == self.binfo:
1804                     # ...and it's even up-to-date...
1805                     if self._local:
1806                         # ...and they'd like a local copy.
1807                         LocalCopy(self, r, None)
1808                         self.store_info(self.binfo)
1809                     return 1
1810             return None
1811         else:
1812             old = self.get_stored_info()
1813             return (old == self.binfo)
1814
1815     def rfile(self):
1816         "__cacheable__"
1817         if not self.exists():
1818             norm_name = _my_normcase(self.name)
1819             for dir in self.dir.get_all_rdirs():
1820                 try: node = dir.entries[norm_name]
1821                 except KeyError: node = dir.file_on_disk(self.name)
1822                 if node and node.exists() and \
1823                    (isinstance(node, File) or isinstance(node, Entry) \
1824                     or not node.is_derived()):
1825                         return node
1826         return self
1827
1828     def rstr(self):
1829         return str(self.rfile())
1830
1831     def cachepath(self):
1832         if not self.fs.CachePath:
1833             return None, None
1834         if self.binfo.bsig is None:
1835             raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1836         # Add the path to the cache signature, because multiple
1837         # targets built by the same action will all have the same
1838         # build signature, and we have to differentiate them somehow.
1839         cache_sig = SCons.Sig.MD5.collect([self.binfo.bsig, self.path])
1840         subdir = string.upper(cache_sig[0])
1841         dir = os.path.join(self.fs.CachePath, subdir)
1842         return dir, os.path.join(dir, cache_sig)
1843
1844     def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1845         return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1846
1847     def must_be_a_Dir(self):
1848         """Called to make sure a Node is a Dir.  Since we're already a
1849         File, this is a TypeError..."""
1850         raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1851
1852 default_fs = None
1853
1854 def find_file(filename, paths, verbose=None):
1855     """
1856     find_file(str, [Dir()]) -> [nodes]
1857
1858     filename - a filename to find
1859     paths - a list of directory path *nodes* to search in.  Can be
1860             represented as a list, a tuple, or a callable that is
1861             called with no arguments and returns the list or tuple.
1862
1863     returns - the node created from the found file.
1864
1865     Find a node corresponding to either a derived file or a file
1866     that exists already.
1867
1868     Only the first file found is returned, and none is returned
1869     if no file is found.
1870     __cacheable__
1871     """
1872     if verbose:
1873         if not SCons.Util.is_String(verbose):
1874             verbose = "find_file"
1875         if not callable(verbose):
1876             verbose = '  %s: ' % verbose
1877             verbose = lambda s, v=verbose: sys.stdout.write(v + s)
1878     else:
1879         verbose = lambda x: x
1880
1881     if callable(paths):
1882         paths = paths()
1883
1884     # Give Entries a chance to morph into Dirs.
1885     paths = map(lambda p: p.must_be_a_Dir(), paths)
1886
1887     filedir, filename = os.path.split(filename)
1888     if filedir:
1889         def filedir_lookup(p, fd=filedir):
1890             try:
1891                 return p.Dir(fd)
1892             except TypeError:
1893                 # We tried to look up a Dir, but it seems there's already
1894                 # a File (or something else) there.  No big.
1895                 return None
1896         paths = filter(None, map(filedir_lookup, paths))
1897
1898     for dir in paths:
1899         verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
1900         node, d = dir.srcdir_find_file(filename)
1901         if node:
1902             verbose("... FOUND '%s' in '%s'\n" % (filename, d))
1903             return node
1904     return None
1905
1906 def find_files(filenames, paths):
1907     """
1908     find_files([str], [Dir()]) -> [nodes]
1909
1910     filenames - a list of filenames to find
1911     paths - a list of directory path *nodes* to search in
1912
1913     returns - the nodes created from the found files.
1914
1915     Finds nodes corresponding to either derived files or files
1916     that exist already.
1917
1918     Only the first file found is returned for each filename,
1919     and any files that aren't found are ignored.
1920     """
1921     nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
1922     return filter(None, nodes)