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