5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
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.
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:
24 # The above copyright notice and this permission notice shall be included
25 # in all copies or substantial portions of the Software.
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.
36 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
48 from SCons.Debug import logInstanceCreation
56 # The max_drift value: by default, use a cached signature value for
57 # any file that's been untouched for more than two days.
58 default_max_drift = 2*24*60*60
61 # We stringify these file system Nodes a lot. Turning a file system Node
62 # into a string is non-trivial, because the final string representation
63 # can depend on a lot of factors: whether it's a derived target or not,
64 # whether it's linked to a repository or source directory, and whether
65 # there's duplication going on. The normal technique for optimizing
66 # calculations like this is to memoize (cache) the string value, so you
67 # only have to do the calculation once.
69 # A number of the above factors, however, can be set after we've already
70 # been asked to return a string for a Node, because a Repository() or
71 # BuildDir() call or the like may not occur until later in SConscript
72 # files. So this variable controls whether we bother trying to save
73 # string values for Nodes. The wrapper interface can set this whenever
74 # they're done mucking with Repository and BuildDir and the other stuff,
75 # to let this module know it can start returning saved string values
80 def save_strings(val):
85 # SCons.Action objects for interacting with the outside world.
87 # The Node.FS methods in this module should use these actions to
88 # create and/or remove files and directories; they should *not* use
89 # os.{link,symlink,unlink,mkdir}(), etc., directly.
91 # Using these SCons.Action objects ensures that descriptions of these
92 # external activities are properly displayed, that the displays are
93 # suppressed when the -s (silent) option is used, and (most importantly)
94 # the actions are disabled when the the -n option is used, in which case
95 # there should be *no* changes to the external file system(s)...
98 def _copy_func(src, dest):
99 shutil.copy2(src, dest)
101 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
103 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
104 'hard-copy', 'soft-copy', 'copy']
106 Link_Funcs = [] # contains the callables of the specified duplication style
108 def set_duplicate(duplicate):
109 # Fill in the Link_Funcs list according to the argument
110 # (discarding those not available on the platform).
112 # Set up the dictionary that maps the argument names to the
113 # underlying implementations. We do this inside this function,
114 # not in the top-level module code, so that we can remap os.link
115 # and os.symlink for testing purposes.
117 _hardlink_func = os.link
118 except AttributeError:
119 _hardlink_func = None
122 _softlink_func = os.symlink
123 except AttributeError:
124 _softlink_func = None
127 'hard' : _hardlink_func,
128 'soft' : _softlink_func,
132 if not duplicate in Valid_Duplicates:
133 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
134 "should be in Valid_Duplicates")
137 for func in string.split(duplicate,'-'):
139 Link_Funcs.append(link_dict[func])
141 def LinkFunc(target, source, env):
142 # Relative paths cause problems with symbolic links, so
143 # we use absolute paths, which may be a problem for people
144 # who want to move their soft-linked src-trees around. Those
145 # people should use the 'hard-copy' mode, softlinks cannot be
146 # used for that; at least I have no idea how ...
147 src = source[0].abspath
148 dest = target[0].abspath
149 dir, file = os.path.split(dest)
150 if dir and not target[0].fs.isdir(dir):
153 # Set a default order of link functions.
154 set_duplicate('hard-soft-copy')
155 # Now link the files with the previously specified order.
156 for func in Link_Funcs:
160 except (IOError, OSError):
161 # An OSError indicates something happened like a permissions
162 # problem or an attempt to symlink across file-system
163 # boundaries. An IOError indicates something like the file
164 # not existing. In either case, keeping trying additional
165 # functions in the list and only raise an error if the last
167 if func == Link_Funcs[-1]:
168 # exception of the last link method (copy) are fatal
174 Link = SCons.Action.Action(LinkFunc, None)
175 def LocalString(target, source, env):
176 return 'Local copy of %s from %s' % (target[0], source[0])
178 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
180 def UnlinkFunc(target, source, env):
182 t.fs.unlink(t.abspath)
185 Unlink = SCons.Action.Action(UnlinkFunc, None)
187 def MkdirFunc(target, source, env):
190 t.fs.mkdir(t.abspath)
193 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
197 def get_MkdirBuilder():
199 if MkdirBuilder is None:
201 # "env" will get filled in by Executor.get_build_env()
202 # calling SCons.Defaults.DefaultEnvironment() when necessary.
203 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
207 name = "MkdirBuilder")
210 def CacheRetrieveFunc(target, source, env):
213 cachedir, cachefile = t.cachepath()
214 if fs.exists(cachefile):
215 if SCons.Action.execute_actions:
216 fs.copy2(cachefile, t.path)
217 st = fs.stat(cachefile)
218 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
222 def CacheRetrieveString(target, source, env):
224 cachedir, cachefile = t.cachepath()
225 if t.fs.exists(cachefile):
226 return "Retrieved `%s' from cache" % t.path
229 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
231 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
233 def CachePushFunc(target, source, env):
236 cachedir, cachefile = t.cachepath()
237 if fs.exists(cachefile):
238 # Don't bother copying it if it's already there.
241 if not fs.isdir(cachedir):
242 fs.makedirs(cachedir)
244 tempfile = cachefile+'.tmp'
246 fs.copy2(t.path, tempfile)
247 fs.rename(tempfile, cachefile)
249 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
250 except (IOError, OSError):
251 # It's possible someone else tried writing the file at the
252 # same time we did, or else that there was some problem like
253 # the CacheDir being on a separate file system that's full.
254 # In any case, inability to push a file to cache doesn't affect
255 # the correctness of the build, so just print a warning.
256 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
257 "Unable to copy %s to cache. Cache file is %s"
258 % (str(target), cachefile))
261 CachePush = SCons.Action.Action(CachePushFunc, None)
268 DefaultSCCSBuilder = None
269 DefaultRCSBuilder = None
271 def get_DefaultSCCSBuilder():
272 global DefaultSCCSBuilder
273 if DefaultSCCSBuilder is None:
275 # "env" will get filled in by Executor.get_build_env()
276 # calling SCons.Defaults.DefaultEnvironment() when necessary.
277 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
278 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
280 name = "DefaultSCCSBuilder")
281 return DefaultSCCSBuilder
283 def get_DefaultRCSBuilder():
284 global DefaultRCSBuilder
285 if DefaultRCSBuilder is None:
287 # "env" will get filled in by Executor.get_build_env()
288 # calling SCons.Defaults.DefaultEnvironment() when necessary.
289 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
290 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
292 name = "DefaultRCSBuilder")
293 return DefaultRCSBuilder
295 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
296 _is_cygwin = sys.platform == "cygwin"
297 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
302 return string.upper(x)
307 def __init__(self, type, do, ignore):
313 self.__call__ = self.do
314 def set_ignore(self):
315 self.__call__ = self.ignore
317 if self.type in list:
322 def do_diskcheck_match(node, predicate, errorfmt):
325 raise TypeError, errorfmt % path
327 def ignore_diskcheck_match(node, predicate, errorfmt):
330 def do_diskcheck_rcs(node, name):
332 rcs_dir = node.rcs_dir
333 except AttributeError:
334 rcs_dir = node.rcs_dir = node.Dir('RCS')
335 return rcs_dir.entry_exists_on_disk(name+',v')
337 def ignore_diskcheck_rcs(node, name):
340 def do_diskcheck_sccs(node, name):
342 sccs_dir = node.sccs_dir
343 except AttributeError:
344 sccs_dir = node.sccs_dir = node.Dir('SCCS')
345 return sccs_dir.entry_exists_on_disk('s.'+name)
347 def ignore_diskcheck_sccs(node, name):
350 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
351 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
352 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
360 def set_diskcheck(list):
361 for dc in diskcheckers:
364 def diskcheck_types():
365 return map(lambda dc: dc.type, diskcheckers)
369 class EntryProxy(SCons.Util.Proxy):
370 def __get_abspath(self):
372 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
373 entry.name + "_abspath")
375 def __get_filebase(self):
376 name = self.get().name
377 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
380 def __get_suffix(self):
381 name = self.get().name
382 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
385 def __get_file(self):
386 name = self.get().name
387 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
389 def __get_base_path(self):
390 """Return the file's directory and file name, with the
393 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
394 entry.name + "_base")
396 def __get_posix_path(self):
397 """Return the path with / as the path separator,
398 regardless of platform."""
403 r = string.replace(entry.get_path(), os.sep, '/')
404 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
406 def __get_win32_path(self):
407 """Return the path with \ as the path separator,
408 regardless of platform."""
413 r = string.replace(entry.get_path(), os.sep, '\\')
414 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_win32")
416 def __get_srcnode(self):
417 return EntryProxy(self.get().srcnode())
419 def __get_srcdir(self):
420 """Returns the directory containing the source node linked to this
421 node via BuildDir(), or the directory of this node if not linked."""
422 return EntryProxy(self.get().srcnode().dir)
424 def __get_rsrcnode(self):
425 return EntryProxy(self.get().srcnode().rfile())
427 def __get_rsrcdir(self):
428 """Returns the directory containing the source node linked to this
429 node via BuildDir(), or the directory of this node if not linked."""
430 return EntryProxy(self.get().srcnode().rfile().dir)
433 return EntryProxy(self.get().dir)
435 dictSpecialAttrs = { "base" : __get_base_path,
436 "posix" : __get_posix_path,
437 "win32" : __get_win32_path,
438 "srcpath" : __get_srcnode,
439 "srcdir" : __get_srcdir,
441 "abspath" : __get_abspath,
442 "filebase" : __get_filebase,
443 "suffix" : __get_suffix,
445 "rsrcpath" : __get_rsrcnode,
446 "rsrcdir" : __get_rsrcdir,
449 def __getattr__(self, name):
450 # This is how we implement the "special" attributes
451 # such as base, posix, srcdir, etc.
453 return self.dictSpecialAttrs[name](self)
456 attr = SCons.Util.Proxy.__getattr__(self, name)
457 except AttributeError:
459 classname = string.split(str(entry.__class__), '.')[-1]
460 if classname[-2:] == "'>":
461 # new-style classes report their name as:
462 # "<class 'something'>"
463 # instead of the classic classes:
465 classname = classname[:-2]
466 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
469 class Base(SCons.Node.Node):
470 """A generic class for file system entries. This class is for
471 when we don't know yet whether the entry being looked up is a file
472 or a directory. Instances of this class can morph into either
473 Dir or File objects by a later, more precise lookup.
475 Note: this class does not define __cmp__ and __hash__ for
476 efficiency reasons. SCons does a lot of comparing of
477 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
478 as fast as possible, which means we want to use Python's built-in
479 object identity comparisons.
482 def __init__(self, name, directory, fs):
483 """Initialize a generic Node.FS.Base object.
485 Call the superclass initialization, take care of setting up
486 our relative and absolute paths, identify our parent
487 directory, and indicate that this node should use
489 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
490 SCons.Node.Node.__init__(self)
495 assert directory, "A directory must be provided"
497 self.abspath = directory.entry_abspath(name)
498 if directory.path == '.':
501 self.path = directory.entry_path(name)
502 if directory.tpath == '.':
505 self.tpath = directory.entry_tpath(name)
506 self.path_elements = directory.path_elements + [self]
509 self.cwd = None # will hold the SConscript directory for target nodes
510 self.duplicate = directory.duplicate
513 """Completely clear a Node.FS.Base object of all its cached
514 state (so that it can be re-evaluated by interfaces that do
515 continuous integration builds).
518 SCons.Node.Node.clear(self)
523 def get_suffix(self):
525 return SCons.Util.splitext(self.name)[1]
531 """A Node.FS.Base object's string representation is its path
535 return self._save_str()
536 return self._get_str()
540 return self._get_str()
543 if self.duplicate or self.is_derived():
544 return self.get_path()
545 return self.srcnode().get_path()
551 try: return self.fs.stat(self.abspath)
552 except os.error: return None
556 return not self.stat() is None
560 return self.rfile().exists()
564 if st: return st[stat.ST_MTIME]
569 if st: return st[stat.ST_SIZE]
574 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
578 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
580 if hasattr(os, 'symlink'):
582 try: st = self.fs.lstat(self.abspath)
583 except os.error: return 0
584 return stat.S_ISLNK(st[stat.ST_MODE])
587 return 0 # no symlinks
589 def is_under(self, dir):
593 return self.dir.is_under(dir)
599 """If this node is in a build path, return the node
600 corresponding to its source file. Otherwise, return
607 srcnode = self.fs.Entry(name, dir.srcdir,
608 klass=self.__class__)
610 name = dir.name + os.sep + name
614 def get_path(self, dir=None):
615 """Return path relative to the current working directory of the
616 Node.FS.Base object that owns us."""
618 dir = self.fs.getcwd()
621 path_elems = self.path_elements
622 try: i = path_elems.index(dir)
623 except ValueError: pass
624 else: path_elems = path_elems[i+1:]
625 path_elems = map(lambda n: n.name, path_elems)
626 return string.join(path_elems, os.sep)
628 def set_src_builder(self, builder):
629 """Set the source code builder for this node."""
630 self.sbuilder = builder
631 if not self.has_builder():
632 self.builder_set(builder)
634 def src_builder(self):
635 """Fetch the source code builder for this node.
637 If there isn't one, we cache the source code builder specified
638 for the directory (which in turn will cache the value from its
639 parent directory, and so on up to the file system root).
643 except AttributeError:
644 scb = self.dir.src_builder()
648 def get_abspath(self):
649 """Get the absolute path of the file."""
652 def for_signature(self):
653 # Return just our name. Even an absolute path would not work,
654 # because that can change thanks to symlinks or remapped network
658 def get_subst_proxy(self):
661 except AttributeError:
662 ret = EntryProxy(self)
667 """This is the class for generic Node.FS entries--that is, things
668 that could be a File or a Dir, but we're just not sure yet.
669 Consequently, the methods in this class really exist just to
670 transform their associated object into the right class when the
671 time comes, and then call the same-named method in the transformed
674 def diskcheck_match(self):
677 def disambiguate(self):
682 self.__class__ = File
688 """We're a generic Entry, but the caller is actually looking for
689 a File at this point, so morph into one."""
690 self.__class__ = File
693 return File.rfile(self)
695 def get_found_includes(self, env, scanner, path):
696 """If we're looking for included files, it's because this Entry
697 is really supposed to be a File itself."""
698 return self.disambiguate().get_found_includes(env, scanner, path)
700 def scanner_key(self):
701 return self.get_suffix()
703 def get_contents(self):
704 """Fetch the contents of the entry.
706 Since this should return the real contents from the file
707 system, we check to see into what sort of subclass we should
710 self.__class__ = File
712 return self.get_contents()
716 return self.get_contents()
718 return '' # avoid errors for dangling symlinks
721 def rel_path(self, other):
722 return self.disambiguate().rel_path(other)
725 """Return if the Entry exists. Check the file system to see
726 what we should turn into first. Assume a file if there's no
728 return self.disambiguate().exists()
730 def calc_signature(self, calc=None):
731 """Return the Entry's calculated signature. Check the file
732 system to see what we should turn into first. Assume a file if
733 there's no directory."""
734 return self.disambiguate().calc_signature(calc)
736 def must_be_a_Dir(self):
737 """Called to make sure a Node is a Dir. Since we're an
738 Entry, we can morph into one."""
743 # This is for later so we can differentiate between Entry the class and Entry
744 # the method of the FS class.
750 if SCons.Memoize.use_memoizer:
751 __metaclass__ = SCons.Memoize.Memoized_Metaclass
753 # This class implements an abstraction layer for operations involving
754 # a local file system. Essentially, this wraps any function in
755 # the os, os.path or shutil modules that we use to actually go do
756 # anything with or to the local file system.
758 # Note that there's a very good chance we'll refactor this part of
759 # the architecture in some way as we really implement the interface(s)
760 # for remote file system Nodes. For example, the right architecture
761 # might be to have this be a subclass instead of a base class.
762 # Nevertheless, we're using this as a first step in that direction.
764 # We're not using chdir() yet because the calling subclass method
765 # needs to use os.chdir() directly to avoid recursion. Will we
766 # really need this one?
767 #def chdir(self, path):
768 # return os.chdir(path)
769 def chmod(self, path, mode):
770 return os.chmod(path, mode)
771 def copy2(self, src, dst):
772 return shutil.copy2(src, dst)
773 def exists(self, path):
774 return os.path.exists(path)
775 def getmtime(self, path):
776 return os.path.getmtime(path)
777 def getsize(self, path):
778 return os.path.getsize(path)
779 def isdir(self, path):
780 return os.path.isdir(path)
781 def isfile(self, path):
782 return os.path.isfile(path)
783 def link(self, src, dst):
784 return os.link(src, dst)
785 def lstat(self, path):
786 return os.lstat(path)
787 def listdir(self, path):
788 return os.listdir(path)
789 def makedirs(self, path):
790 return os.makedirs(path)
791 def mkdir(self, path):
792 return os.mkdir(path)
793 def rename(self, old, new):
794 return os.rename(old, new)
795 def stat(self, path):
797 def symlink(self, src, dst):
798 return os.symlink(src, dst)
799 def open(self, path):
801 def unlink(self, path):
802 return os.unlink(path)
804 if hasattr(os, 'symlink'):
805 def islink(self, path):
806 return os.path.islink(path)
808 def islink(self, path):
809 return 0 # no symlinks
811 if SCons.Memoize.use_old_memoization():
813 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
814 def __init__(self, *args, **kw):
815 apply(_FSBase.__init__, (self,)+args, kw)
816 SCons.Memoize.Memoizer.__init__(self)
820 # # Skeleton for the obvious methods we might need from the
821 # # abstraction layer for a remote filesystem.
822 # def upload(self, local_src, remote_dst):
824 # def download(self, remote_src, local_dst):
830 def __init__(self, path = None):
831 """Initialize the Node.FS subsystem.
833 The supplied path is the top of the source tree, where we
834 expect to find the top-level build file. If no path is
835 supplied, the current directory is the default.
837 The path argument must be a valid absolute path.
839 if __debug__: logInstanceCreation(self, 'Node.FS')
841 self.SConstruct_dir = None
842 self.CachePath = None
843 self.cache_force = None
844 self.cache_show = None
845 self.max_drift = default_max_drift
848 self.pathTop = os.getcwd()
852 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
857 def clear_cache(self):
861 def set_SConstruct_dir(self, dir):
862 self.SConstruct_dir = dir
864 def get_max_drift(self):
865 return self.max_drift
867 def set_max_drift(self, max_drift):
868 self.max_drift = max_drift
873 def __checkClass(self, node, klass):
874 if isinstance(node, klass) or klass == Entry:
876 if node.__class__ == Entry:
877 node.__class__ = klass
880 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
881 (node.__class__.__name__, node.path, klass.__name__)
883 def _doLookup(self, fsclass, name, directory = None, create = 1):
884 """This method differs from the File and Dir factory methods in
885 one important way: the meaning of the directory parameter.
886 In this method, if directory is None or not supplied, the supplied
887 name is expected to be an absolute path. If you try to look up a
888 relative path with directory=None, then an AssertionError will be
893 # This is a stupid hack to compensate for the fact that
894 # the POSIX and Win32 versions of os.path.normpath() behave
895 # differently in older versions of Python. In particular,
897 # os.path.normpath('./') == '.'
899 # os.path.normpath('./') == ''
900 # os.path.normpath('.\\') == ''
902 # This is a definite bug in the Python library, but we have
905 path_orig = string.split(name, os.sep)
906 path_norm = string.split(_my_normcase(name), os.sep)
908 first_orig = path_orig.pop(0) # strip first element
909 first_norm = path_norm.pop(0) # strip first element
911 drive, path_first = os.path.splitdrive(first_orig)
913 path_orig = [ path_first, ] + path_orig
914 path_norm = [ _my_normcase(path_first), ] + path_norm
916 drive = _my_normcase(drive)
919 directory = self.Root[drive]
922 raise SCons.Errors.UserError
923 directory = RootDir(drive, self)
924 self.Root[drive] = directory
929 last_orig = path_orig.pop() # strip last element
930 last_norm = path_norm.pop() # strip last element
932 # Lookup the directory
933 for orig, norm in map(None, path_orig, path_norm):
935 entries = directory.entries
936 except AttributeError:
937 # We tried to look up the entry in either an Entry or
938 # a File. Give whatever it is a chance to do what's
939 # appropriate: morph into a Dir or raise an exception.
940 directory.must_be_a_Dir()
941 entries = directory.entries
943 directory = entries[norm]
946 raise SCons.Errors.UserError
948 d = Dir(orig, directory, self)
950 # Check the file system (or not, as configured) to make
951 # sure there isn't already a file there.
954 directory.entries[norm] = d
955 directory.add_wkid(d)
958 directory.must_be_a_Dir()
961 e = directory.entries[last_norm]
964 raise SCons.Errors.UserError
966 result = fsclass(last_orig, directory, self)
968 # Check the file system (or not, as configured) to make
969 # sure there isn't already a directory at the path on
970 # disk where we just created a File node, and vice versa.
971 result.diskcheck_match()
973 directory.entries[last_norm] = result
974 directory.add_wkid(result)
976 result = self.__checkClass(e, fsclass)
979 def _transformPath(self, name, directory):
980 """Take care of setting up the correct top-level directory,
981 usually in preparation for a call to doLookup().
983 If the path name is prepended with a '#', then it is unconditionally
984 interpreted as relative to the top-level directory of this FS.
986 If directory is None, and name is a relative path,
987 then the same applies.
989 if name and name[0] == '#':
992 if name and (name[0] == os.sep or name[0] == '/'):
993 # Correct such that '#/foo' is equivalent
996 name = os.path.join('.', os.path.normpath(name))
998 directory = self._cwd
999 return (os.path.normpath(name), directory)
1001 def chdir(self, dir, change_os_dir=0):
1002 """Change the current working directory for lookups.
1003 If change_os_dir is true, we will also change the "real" cwd
1011 os.chdir(dir.abspath)
1016 def Entry(self, name, directory = None, create = 1, klass=None):
1017 """Lookup or create a generic Entry node with the specified name.
1018 If the name is a relative path (begins with ./, ../, or a file
1019 name), then it is looked up relative to the supplied directory
1020 node, or to the top level directory of the FS (supplied at
1021 construction time) if no directory is supplied.
1027 if isinstance(name, Base):
1028 return self.__checkClass(name, klass)
1030 if directory and not isinstance(directory, Dir):
1031 directory = self.Dir(directory)
1032 name, directory = self._transformPath(name, directory)
1033 return self._doLookup(klass, name, directory, create)
1035 def File(self, name, directory = None, create = 1):
1036 """Lookup or create a File node with the specified name. If
1037 the name is a relative path (begins with ./, ../, or a file name),
1038 then it is looked up relative to the supplied directory node,
1039 or to the top level directory of the FS (supplied at construction
1040 time) if no directory is supplied.
1042 This method will raise TypeError if a directory is found at the
1046 return self.Entry(name, directory, create, File)
1048 def Dir(self, name, directory = None, create = 1):
1049 """Lookup or create a Dir node with the specified name. If
1050 the name is a relative path (begins with ./, ../, or a file name),
1051 then it is looked up relative to the supplied directory node,
1052 or to the top level directory of the FS (supplied at construction
1053 time) if no directory is supplied.
1055 This method will raise TypeError if a normal file is found at the
1059 return self.Entry(name, directory, create, Dir)
1061 def BuildDir(self, build_dir, src_dir, duplicate=1):
1062 """Link the supplied build directory to the source directory
1063 for purposes of building files."""
1065 if not isinstance(src_dir, SCons.Node.Node):
1066 src_dir = self.Dir(src_dir)
1067 if not isinstance(build_dir, SCons.Node.Node):
1068 build_dir = self.Dir(build_dir)
1069 if src_dir.is_under(build_dir):
1070 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1071 if build_dir.srcdir:
1072 if build_dir.srcdir == src_dir:
1073 return # We already did this.
1074 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1075 build_dir.link(src_dir, duplicate)
1077 def Repository(self, *dirs):
1078 """Specify Repository directories to search."""
1080 if not isinstance(d, SCons.Node.Node):
1082 self.Top.addRepository(d)
1084 def Rfindalldirs(self, pathlist, cwd):
1086 if SCons.Util.is_String(pathlist):
1087 pathlist = string.split(pathlist, os.pathsep)
1088 if not SCons.Util.is_List(pathlist):
1089 pathlist = [pathlist]
1091 for path in filter(None, pathlist):
1092 if isinstance(path, SCons.Node.Node):
1095 path, dir = self._transformPath(path, cwd)
1097 result.extend(dir.get_all_rdirs())
1100 def CacheDir(self, path):
1101 self.CachePath = path
1103 def build_dir_target_climb(self, orig, dir, tail):
1104 """Create targets in corresponding build directories
1106 Climb the directory tree, and look up path names
1107 relative to any linked build directories we find.
1112 fmt = "building associated BuildDir targets: %s"
1115 for bd in dir.build_dirs:
1116 if start_dir.is_under(bd):
1117 # If already in the build-dir location, don't reflect
1118 return [orig], fmt % str(orig)
1119 p = apply(os.path.join, [bd.path] + tail)
1120 targets.append(self.Entry(p))
1121 tail = [dir.name] + tail
1124 message = fmt % string.join(map(str, targets))
1125 return targets, message
1128 """A class for directories in a file system.
1131 def __init__(self, name, directory, fs):
1132 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1133 Base.__init__(self, name, directory, fs)
1137 """Turn a file system Node (either a freshly initialized directory
1138 object or a separate Entry object) into a proper directory object.
1140 Set up this directory's entries and hook it into the file
1141 system tree. Specify that directories (this Node) don't use
1142 signatures for calculating whether they're current.
1145 self.repositories = []
1149 self.entries['.'] = self
1150 self.entries['..'] = self.dir
1153 self._sconsign = None
1154 self.build_dirs = []
1156 # Don't just reset the executor, replace its action list,
1157 # because it might have some pre-or post-actions that need to
1159 self.builder = get_MkdirBuilder()
1160 self.get_executor().set_action_list(self.builder.action)
1162 def diskcheck_match(self):
1163 diskcheck_match(self, self.fs.isfile,
1164 "File %s found where directory expected.")
1166 def disambiguate(self):
1169 def __clearRepositoryCache(self, duplicate=None):
1170 """Called when we change the repository(ies) for a directory.
1171 This clears any cached information that is invalidated by changing
1174 for node in self.entries.values():
1175 if node != self.dir:
1176 if node != self and isinstance(node, Dir):
1177 node.__clearRepositoryCache(duplicate)
1182 except AttributeError:
1184 if duplicate != None:
1185 node.duplicate=duplicate
1187 def __resetDuplicate(self, node):
1189 node.duplicate = node.get_dir().duplicate
1191 def Entry(self, name):
1192 """Create an entry node named 'name' relative to this directory."""
1193 return self.fs.Entry(name, self)
1195 def Dir(self, name):
1196 """Create a directory node named 'name' relative to this directory."""
1197 return self.fs.Dir(name, self)
1199 def File(self, name):
1200 """Create a file node named 'name' relative to this directory."""
1201 return self.fs.File(name, self)
1203 def link(self, srcdir, duplicate):
1204 """Set this directory as the build directory for the
1205 supplied source directory."""
1206 self.srcdir = srcdir
1207 self.duplicate = duplicate
1208 self.__clearRepositoryCache(duplicate)
1209 srcdir.build_dirs.append(self)
1211 def getRepositories(self):
1212 """Returns a list of repositories for this directory.
1214 if self.srcdir and not self.duplicate:
1215 return self.srcdir.get_all_rdirs() + self.repositories
1216 return self.repositories
1218 def get_all_rdirs(self):
1224 for rep in dir.getRepositories():
1225 result.append(rep.Dir(fname))
1226 fname = dir.name + os.sep + fname
1230 def addRepository(self, dir):
1231 if dir != self and not dir in self.repositories:
1232 self.repositories.append(dir)
1234 self.__clearRepositoryCache()
1237 return self.entries['..']
1239 def rel_path(self, other):
1240 """Return a path to "other" relative to this directory.
1242 if isinstance(other, Dir):
1248 except AttributeError:
1251 return name and name[0] or '.'
1253 for x, y in map(None, self.path_elements, other.path_elements):
1257 path_elems = ['..']*(len(self.path_elements)-i) \
1258 + map(lambda n: n.name, other.path_elements[i:]) \
1261 return string.join(path_elems, os.sep)
1264 if not self.implicit is None:
1267 self.implicit_dict = {}
1268 self._children_reset()
1270 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1271 deps = filter(dont_scan, self.entries.keys())
1272 # keys() is going to give back the entries in an internal,
1273 # unsorted order. Sort 'em so the order is deterministic.
1275 entries = map(lambda n, e=self.entries: e[n], deps)
1277 self._add_child(self.implicit, self.implicit_dict, entries)
1279 def get_found_includes(self, env, scanner, path):
1280 """Return the included implicit dependencies in this file.
1281 Cache results so we only scan the file once per path
1282 regardless of how many times this information is requested.
1286 # Clear cached info for this Node. If we already visited this
1287 # directory on our walk down the tree (because we didn't know at
1288 # that point it was being used as the source for another Node)
1289 # then we may have calculated build signature before realizing
1290 # we had to scan the disk. Now that we have to, though, we need
1291 # to invalidate the old calculated signature so that any node
1292 # dependent on our directory structure gets one that includes
1293 # info about everything on disk.
1295 return scanner(self, env, path)
1297 def build(self, **kw):
1298 """A null "builder" for directories."""
1300 if not self.builder is MkdirBuilder:
1301 apply(SCons.Node.Node.build, [self,], kw)
1304 """Create this directory, silently and without worrying about
1305 whether the builder is the default or not."""
1311 listDirs.append(parent)
1314 raise SCons.Errors.StopError, parent.path
1317 for dirnode in listDirs:
1319 # Don't call dirnode.build(), call the base Node method
1320 # directly because we definitely *must* create this
1321 # directory. The dirnode.build() method will suppress
1322 # the build if it's the default builder.
1323 SCons.Node.Node.build(dirnode)
1324 dirnode.get_executor().nullify()
1325 # The build() action may or may not have actually
1326 # created the directory, depending on whether the -n
1327 # option was used or not. Delete the _exists and
1328 # _rexists attributes so they can be reevaluated.
1333 def multiple_side_effect_has_builder(self):
1335 return not self.builder is MkdirBuilder and self.has_builder()
1337 def alter_targets(self):
1338 """Return any corresponding targets in a build directory.
1340 return self.fs.build_dir_target_climb(self, self, [])
1342 def scanner_key(self):
1343 """A directory does not get scanned."""
1346 def get_contents(self):
1347 """Return aggregate contents of all our children."""
1348 contents = cStringIO.StringIO()
1349 for kid in self.children():
1350 contents.write(kid.get_contents())
1351 return contents.getvalue()
1356 def do_duplicate(self, src):
1359 def current(self, calc=None):
1360 """If all of our children were up-to-date, then this
1361 directory was up-to-date, too."""
1362 if not self.builder is MkdirBuilder and not self.exists():
1365 for kid in self.children():
1367 if s and (not state or s > state):
1370 if state == 0 or state == SCons.Node.up_to_date:
1377 if not self.exists():
1378 norm_name = _my_normcase(self.name)
1379 for dir in self.dir.get_all_rdirs():
1380 try: node = dir.entries[norm_name]
1381 except KeyError: node = dir.dir_on_disk(self.name)
1382 if node and node.exists() and \
1383 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1388 """Return the .sconsign file info for this directory,
1389 creating it first if necessary."""
1390 if not self._sconsign:
1391 import SCons.SConsign
1392 self._sconsign = SCons.SConsign.ForDirectory(self)
1393 return self._sconsign
1396 """Dir has a special need for srcnode()...if we
1397 have a srcdir attribute set, then that *is* our srcnode."""
1400 return Base.srcnode(self)
1402 def get_timestamp(self):
1403 """Return the latest timestamp from among our children"""
1405 for kid in self.children():
1406 if kid.get_timestamp() > stamp:
1407 stamp = kid.get_timestamp()
1410 def entry_abspath(self, name):
1411 return self.abspath + os.sep + name
1413 def entry_path(self, name):
1414 return self.path + os.sep + name
1416 def entry_tpath(self, name):
1417 return self.tpath + os.sep + name
1419 def must_be_a_Dir(self):
1420 """Called to make sure a Node is a Dir. Since we're already
1421 one, this is a no-op for us."""
1424 def entry_exists_on_disk(self, name):
1427 d = self.on_disk_entries
1428 except AttributeError:
1431 entries = os.listdir(self.abspath)
1435 for entry in entries:
1437 self.on_disk_entries = d
1438 return d.has_key(name)
1440 def srcdir_list(self):
1448 d = dir.srcdir.Dir(dirname)
1450 # Shouldn't source from something in the build path:
1451 # build_dir is probably under src_dir, in which case
1452 # we are reflecting.
1455 dirname = dir.name + os.sep + dirname
1460 def srcdir_duplicate(self, name):
1461 for dir in self.srcdir_list():
1462 if dir.entry_exists_on_disk(name):
1463 srcnode = dir.File(name)
1465 node = self.File(name)
1466 node.do_duplicate(srcnode)
1472 def srcdir_find_file(self, filename):
1475 if (isinstance(node, File) or isinstance(node, Entry)) and \
1476 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1480 norm_name = _my_normcase(filename)
1482 for rdir in self.get_all_rdirs():
1483 try: node = rdir.entries[norm_name]
1484 except KeyError: node = rdir.file_on_disk(filename)
1485 else: node = func(node)
1489 for srcdir in self.srcdir_list():
1490 for rdir in srcdir.get_all_rdirs():
1491 try: node = rdir.entries[norm_name]
1492 except KeyError: node = rdir.file_on_disk(filename)
1493 else: node = func(node)
1495 return File(filename, self, self.fs), srcdir
1499 def dir_on_disk(self, name):
1500 if self.entry_exists_on_disk(name):
1501 try: return self.Dir(name)
1502 except TypeError: pass
1505 def file_on_disk(self, name):
1506 if self.entry_exists_on_disk(name) or \
1507 diskcheck_rcs(self, name) or \
1508 diskcheck_sccs(self, name):
1509 try: return self.File(name)
1510 except TypeError: pass
1511 return self.srcdir_duplicate(name)
1514 """A class for the root directory of a file system.
1516 This is the same as a Dir class, except that the path separator
1517 ('/' or '\\') is actually part of the name, so we don't need to
1518 add a separator when creating the path names of entries within
1521 def __init__(self, name, fs):
1522 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1523 # We're going to be our own parent directory (".." entry and .dir
1524 # attribute) so we have to set up some values so Base.__init__()
1525 # won't gag won't it calls some of our methods.
1529 self.path_elements = []
1531 Base.__init__(self, name, self, fs)
1533 # Now set our paths to what we really want them to be: the
1534 # initial drive letter (the name) plus the directory separator.
1535 self.abspath = name + os.sep
1536 self.path = name + os.sep
1537 self.tpath = name + os.sep
1543 def entry_abspath(self, name):
1544 return self.abspath + name
1546 def entry_path(self, name):
1547 return self.path + name
1549 def entry_tpath(self, name):
1550 return self.tpath + name
1552 def is_under(self, dir):
1564 def src_builder(self):
1567 class NodeInfo(SCons.Node.NodeInfo):
1568 def __cmp__(self, other):
1569 try: return cmp(self.bsig, other.bsig)
1570 except AttributeError: return 1
1571 def update(self, node):
1572 self.timestamp = node.get_timestamp()
1573 self.size = node.getsize()
1575 class BuildInfo(SCons.Node.BuildInfo):
1576 def __init__(self, node):
1577 SCons.Node.BuildInfo.__init__(self, node)
1579 def convert_to_sconsign(self):
1580 """Convert this BuildInfo object for writing to a .sconsign file
1582 We hung onto the node that we refer to so that we can translate
1583 the lists of bsources, bdepends and bimplicit Nodes into strings
1584 relative to the node, but we don't want to write out that Node
1585 itself to the .sconsign file, so we delete the attribute in
1588 rel_path = self.node.rel_path
1589 delattr(self, 'node')
1590 for attr in ['bsources', 'bdepends', 'bimplicit']:
1592 val = getattr(self, attr)
1593 except AttributeError:
1596 setattr(self, attr, map(rel_path, val))
1597 def convert_from_sconsign(self, dir, name):
1598 """Convert a newly-read BuildInfo object for in-SCons use
1600 An on-disk BuildInfo comes without a reference to the node
1601 for which it's intended, so we have to convert the arguments
1602 and add back a self.node attribute. The bsources, bdepends and
1603 bimplicit lists all come from disk as paths relative to that node,
1604 so convert them to actual Nodes for use by the rest of SCons.
1606 self.node = dir.Entry(name)
1607 Entry_func = self.node.dir.Entry
1608 for attr in ['bsources', 'bdepends', 'bimplicit']:
1610 val = getattr(self, attr)
1611 except AttributeError:
1614 setattr(self, attr, map(Entry_func, val))
1617 """A class for files in a file system.
1619 def diskcheck_match(self):
1620 diskcheck_match(self, self.fs.isdir,
1621 "Directory %s found where file expected.")
1623 def __init__(self, name, directory, fs):
1624 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1625 Base.__init__(self, name, directory, fs)
1628 def Entry(self, name):
1629 """Create an entry node named 'name' relative to
1630 the SConscript directory of this file."""
1631 return self.fs.Entry(name, self.cwd)
1633 def Dir(self, name):
1634 """Create a directory node named 'name' relative to
1635 the SConscript directory of this file."""
1636 return self.fs.Dir(name, self.cwd)
1638 def Dirs(self, pathlist):
1639 """Create a list of directories relative to the SConscript
1640 directory of this file."""
1641 return map(lambda p, s=self: s.Dir(p), pathlist)
1643 def File(self, name):
1644 """Create a file node named 'name' relative to
1645 the SConscript directory of this file."""
1646 return self.fs.File(name, self.cwd)
1648 def RDirs(self, pathlist):
1649 """Search for a list of directories in the Repository list."""
1650 return self.fs.Rfindalldirs(pathlist, self.cwd)
1653 """Turn a file system node into a File object. __cache_reset__"""
1654 self.scanner_paths = {}
1655 if not hasattr(self, '_local'):
1658 def disambiguate(self):
1661 def scanner_key(self):
1662 return self.get_suffix()
1664 def get_contents(self):
1665 if not self.rexists():
1667 return open(self.rfile().abspath, "rb").read()
1669 def get_timestamp(self):
1671 return self.rfile().getmtime()
1675 def store_info(self, obj):
1676 # Merge our build information into the already-stored entry.
1677 # This accomodates "chained builds" where a file that's a target
1678 # in one build (SConstruct file) is a source in a different build.
1679 # See test/chained-build.py for the use case.
1680 entry = self.get_stored_info()
1682 self.dir.sconsign().set_entry(self.name, entry)
1684 def get_stored_info(self):
1687 stored = self.dir.sconsign().get_entry(self.name)
1688 except (KeyError, OSError):
1689 return self.new_binfo()
1691 if not hasattr(stored, 'ninfo'):
1692 # Transition: The .sconsign file entry has no NodeInfo
1693 # object, which means it's a slightly older BuildInfo.
1694 # Copy over the relevant attributes.
1695 ninfo = stored.ninfo = self.new_ninfo()
1696 for attr in ninfo.__dict__.keys():
1698 setattr(ninfo, attr, getattr(stored, attr))
1699 except AttributeError:
1703 def get_stored_implicit(self):
1704 binfo = self.get_stored_info()
1705 try: return binfo.bimplicit
1706 except AttributeError: return None
1708 def rel_path(self, other):
1709 return self.dir.rel_path(other)
1711 def get_found_includes(self, env, scanner, path):
1712 """Return the included implicit dependencies in this file.
1713 Cache results so we only scan the file once per path
1714 regardless of how many times this information is requested.
1718 return scanner(self, env, path)
1720 def _createDir(self):
1721 # ensure that the directories for this node are
1725 def retrieve_from_cache(self):
1726 """Try to retrieve the node's content from a cache
1728 This method is called from multiple threads in a parallel build,
1729 so only do thread safe stuff here. Do thread unsafe stuff in
1732 Note that there's a special trick here with the execute flag
1733 (one that's not normally done for other actions). Basically
1734 if the user requested a noexec (-n) build, then
1735 SCons.Action.execute_actions is set to 0 and when any action
1736 is called, it does its showing but then just returns zero
1737 instead of actually calling the action execution operation.
1738 The problem for caching is that if the file does NOT exist in
1739 cache then the CacheRetrieveString won't return anything to
1740 show for the task, but the Action.__call__ won't call
1741 CacheRetrieveFunc; instead it just returns zero, which makes
1742 the code below think that the file *was* successfully
1743 retrieved from the cache, therefore it doesn't do any
1744 subsequent building. However, the CacheRetrieveString didn't
1745 print anything because it didn't actually exist in the cache,
1746 and no more build actions will be performed, so the user just
1747 sees nothing. The fix is to tell Action.__call__ to always
1748 execute the CacheRetrieveFunc and then have the latter
1749 explicitly check SCons.Action.execute_actions itself.
1751 Returns true iff the node was successfully retrieved.
1753 b = self.is_derived()
1754 if not b and not self.has_src_builder():
1756 if b and self.fs.CachePath:
1757 if self.fs.cache_show:
1758 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1759 self.build(presub=0, execute=0)
1761 elif CacheRetrieve(self, [], None, execute=1) == 0:
1766 """Called just after this node is successfully built.
1768 # Push this file out to cache before the superclass Node.built()
1769 # method has a chance to clear the build signature, which it
1770 # will do if this file has a source scanner.
1771 if self.fs.CachePath and self.exists():
1772 CachePush(self, [], None)
1773 self.fs.clear_cache()
1774 SCons.Node.Node.built(self)
1777 if self.fs.CachePath and self.fs.cache_force and self.exists():
1778 CachePush(self, None, None)
1780 def has_src_builder(self):
1781 """Return whether this Node has a source builder or not.
1783 If this Node doesn't have an explicit source code builder, this
1784 is where we figure out, on the fly, if there's a transparent
1785 source code builder for it.
1787 Note that if we found a source builder, we also set the
1788 self.builder attribute, so that all of the methods that actually
1789 *build* this file don't have to do anything different.
1793 except AttributeError:
1797 scb = self.dir.src_builder()
1799 if diskcheck_sccs(self.dir, self.name):
1800 scb = get_DefaultSCCSBuilder()
1801 elif diskcheck_rcs(self.dir, self.name):
1802 scb = get_DefaultRCSBuilder()
1806 self.builder_set(scb)
1808 return not scb is None
1810 def alter_targets(self):
1811 """Return any corresponding targets in a build directory.
1813 if self.is_derived():
1815 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1817 def is_pseudo_derived(self):
1819 return self.has_src_builder()
1821 def _rmv_existing(self):
1823 Unlink(self, [], None)
1826 """Prepare for this file to be created."""
1827 SCons.Node.Node.prepare(self)
1829 if self.get_state() != SCons.Node.up_to_date:
1831 if self.is_derived() and not self.precious:
1832 self._rmv_existing()
1836 except SCons.Errors.StopError, drive:
1837 desc = "No drive `%s' for target `%s'." % (drive, self)
1838 raise SCons.Errors.StopError, desc
1841 """Remove this file."""
1842 if self.exists() or self.islink():
1843 self.fs.unlink(self.path)
1847 def do_duplicate(self, src):
1850 Unlink(self, None, None)
1851 except SCons.Errors.BuildError:
1854 Link(self, src, None)
1855 except SCons.Errors.BuildError, e:
1856 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1857 raise SCons.Errors.StopError, desc
1859 # The Link() action may or may not have actually
1860 # created the file, depending on whether the -n
1861 # option was used or not. Delete the _exists and
1862 # _rexists attributes so they can be reevaluated.
1867 # Duplicate from source path if we are set up to do this.
1868 if self.duplicate and not self.is_derived() and not self.linked:
1869 src = self.srcnode()
1871 return Base.exists(self)
1872 # At this point, src is meant to be copied in a build directory.
1874 if src.abspath != self.abspath:
1876 self.do_duplicate(src)
1877 # Can't return 1 here because the duplication might
1878 # not actually occur if the -n option is being used.
1880 # The source file does not exist. Make sure no old
1881 # copy remains in the build directory.
1882 if Base.exists(self) or self.islink():
1883 self.fs.unlink(self.path)
1884 # Return None explicitly because the Base.exists() call
1885 # above will have cached its value if the file existed.
1887 return Base.exists(self)
1890 # SIGNATURE SUBSYSTEM
1893 def new_binfo(self):
1894 return BuildInfo(self)
1896 def new_ninfo(self):
1901 def get_csig(self, calc=None):
1903 Generate a node's content signature, the digested signature
1907 cache - alternate node to use for the signature cache
1908 returns - the content signature
1911 return self.binfo.ninfo.csig
1912 except AttributeError:
1916 calc = self.calculator()
1918 max_drift = self.fs.max_drift
1919 mtime = self.get_timestamp()
1920 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
1924 old = self.get_stored_info().ninfo
1926 if old.timestamp and old.csig and old.timestamp == mtime:
1928 except AttributeError:
1931 csig = calc.module.signature(self)
1933 binfo = self.get_binfo()
1939 self.store_info(binfo)
1947 def current(self, calc=None):
1948 self.binfo = self.gen_binfo(calc)
1952 if self.always_build:
1954 if not self.exists():
1955 # The file doesn't exist locally...
1958 # ...but there is one in a Repository...
1959 old = r.get_stored_info()
1960 new = self.get_binfo()
1962 # ...and it's even up-to-date...
1964 # ...and they'd like a local copy.
1965 LocalCopy(self, r, None)
1966 self.store_info(new)
1970 old = self.get_stored_info()
1971 new = self.get_binfo()
1976 if not self.exists():
1977 norm_name = _my_normcase(self.name)
1978 for dir in self.dir.get_all_rdirs():
1979 try: node = dir.entries[norm_name]
1980 except KeyError: node = dir.file_on_disk(self.name)
1981 if node and node.exists() and \
1982 (isinstance(node, File) or isinstance(node, Entry) \
1983 or not node.is_derived()):
1988 return str(self.rfile())
1990 def cachepath(self):
1991 if not self.fs.CachePath:
1993 ninfo = self.get_binfo().ninfo
1994 if not hasattr(ninfo, 'bsig'):
1995 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
1996 elif ninfo.bsig is None:
1997 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1998 # Add the path to the cache signature, because multiple
1999 # targets built by the same action will all have the same
2000 # build signature, and we have to differentiate them somehow.
2001 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
2002 subdir = string.upper(cache_sig[0])
2003 dir = os.path.join(self.fs.CachePath, subdir)
2004 return dir, os.path.join(dir, cache_sig)
2006 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
2007 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
2009 def must_be_a_Dir(self):
2010 """Called to make sure a Node is a Dir. Since we're already a
2011 File, this is a TypeError..."""
2012 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
2016 def find_file(filename, paths, verbose=None):
2018 find_file(str, [Dir()]) -> [nodes]
2020 filename - a filename to find
2021 paths - a list of directory path *nodes* to search in. Can be
2022 represented as a list, a tuple, or a callable that is
2023 called with no arguments and returns the list or tuple.
2025 returns - the node created from the found file.
2027 Find a node corresponding to either a derived file or a file
2028 that exists already.
2030 Only the first file found is returned, and none is returned
2031 if no file is found.
2035 if not SCons.Util.is_String(verbose):
2036 verbose = "find_file"
2037 if not callable(verbose):
2038 verbose = ' %s: ' % verbose
2039 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2041 verbose = lambda x: x
2046 # Give Entries a chance to morph into Dirs.
2047 paths = map(lambda p: p.must_be_a_Dir(), paths)
2049 filedir, filename = os.path.split(filename)
2051 def filedir_lookup(p, fd=filedir):
2055 # We tried to look up a Dir, but it seems there's already
2056 # a File (or something else) there. No big.
2058 paths = filter(None, map(filedir_lookup, paths))
2061 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2062 node, d = dir.srcdir_find_file(filename)
2064 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2068 def find_files(filenames, paths):
2070 find_files([str], [Dir()]) -> [nodes]
2072 filenames - a list of filenames to find
2073 paths - a list of directory path *nodes* to search in
2075 returns - the nodes created from the found files.
2077 Finds nodes corresponding to either derived files or files
2080 Only the first file found is returned for each filename,
2081 and any files that aren't found are ignored.
2083 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2084 return filter(None, nodes)