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):
331 rcspath = 'RCS' + os.sep + name+',v'
332 return node.entry_exists_on_disk(rcspath)
334 def ignore_diskcheck_rcs(node, name):
337 def do_diskcheck_sccs(node, name):
338 sccspath = 'SCCS' + os.sep + 's.'+name
339 return node.entry_exists_on_disk(sccspath)
341 def ignore_diskcheck_sccs(node, name):
344 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
345 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
346 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
354 def set_diskcheck(list):
355 for dc in diskcheckers:
358 def diskcheck_types():
359 return map(lambda dc: dc.type, diskcheckers)
363 class EntryProxy(SCons.Util.Proxy):
364 def __get_abspath(self):
366 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
367 entry.name + "_abspath")
369 def __get_filebase(self):
370 name = self.get().name
371 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
374 def __get_suffix(self):
375 name = self.get().name
376 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
379 def __get_file(self):
380 name = self.get().name
381 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
383 def __get_base_path(self):
384 """Return the file's directory and file name, with the
387 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
388 entry.name + "_base")
390 def __get_posix_path(self):
391 """Return the path with / as the path separator,
392 regardless of platform."""
397 r = string.replace(entry.get_path(), os.sep, '/')
398 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
400 def __get_win32_path(self):
401 """Return the path with \ as the path separator,
402 regardless of platform."""
407 r = string.replace(entry.get_path(), os.sep, '\\')
408 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_win32")
410 def __get_srcnode(self):
411 return EntryProxy(self.get().srcnode())
413 def __get_srcdir(self):
414 """Returns the directory containing the source node linked to this
415 node via BuildDir(), or the directory of this node if not linked."""
416 return EntryProxy(self.get().srcnode().dir)
418 def __get_rsrcnode(self):
419 return EntryProxy(self.get().srcnode().rfile())
421 def __get_rsrcdir(self):
422 """Returns the directory containing the source node linked to this
423 node via BuildDir(), or the directory of this node if not linked."""
424 return EntryProxy(self.get().srcnode().rfile().dir)
427 return EntryProxy(self.get().dir)
429 dictSpecialAttrs = { "base" : __get_base_path,
430 "posix" : __get_posix_path,
431 "win32" : __get_win32_path,
432 "srcpath" : __get_srcnode,
433 "srcdir" : __get_srcdir,
435 "abspath" : __get_abspath,
436 "filebase" : __get_filebase,
437 "suffix" : __get_suffix,
439 "rsrcpath" : __get_rsrcnode,
440 "rsrcdir" : __get_rsrcdir,
443 def __getattr__(self, name):
444 # This is how we implement the "special" attributes
445 # such as base, posix, srcdir, etc.
447 return self.dictSpecialAttrs[name](self)
450 attr = SCons.Util.Proxy.__getattr__(self, name)
451 except AttributeError:
453 classname = string.split(str(entry.__class__), '.')[-1]
454 if classname[-2:] == "'>":
455 # new-style classes report their name as:
456 # "<class 'something'>"
457 # instead of the classic classes:
459 classname = classname[:-2]
460 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
463 class Base(SCons.Node.Node):
464 """A generic class for file system entries. This class is for
465 when we don't know yet whether the entry being looked up is a file
466 or a directory. Instances of this class can morph into either
467 Dir or File objects by a later, more precise lookup.
469 Note: this class does not define __cmp__ and __hash__ for
470 efficiency reasons. SCons does a lot of comparing of
471 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
472 as fast as possible, which means we want to use Python's built-in
473 object identity comparisons.
476 def __init__(self, name, directory, fs):
477 """Initialize a generic Node.FS.Base object.
479 Call the superclass initialization, take care of setting up
480 our relative and absolute paths, identify our parent
481 directory, and indicate that this node should use
483 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
484 SCons.Node.Node.__init__(self)
489 assert directory, "A directory must be provided"
491 self.abspath = directory.entry_abspath(name)
492 if directory.path == '.':
495 self.path = directory.entry_path(name)
496 if directory.tpath == '.':
499 self.tpath = directory.entry_tpath(name)
500 self.path_elements = directory.path_elements + [self]
503 self.cwd = None # will hold the SConscript directory for target nodes
504 self.duplicate = directory.duplicate
507 """Completely clear a Node.FS.Base object of all its cached
508 state (so that it can be re-evaluated by interfaces that do
509 continuous integration builds).
512 SCons.Node.Node.clear(self)
517 def get_suffix(self):
519 return SCons.Util.splitext(self.name)[1]
525 """A Node.FS.Base object's string representation is its path
529 return self._save_str()
530 return self._get_str()
534 return self._get_str()
537 if self.duplicate or self.is_derived():
538 return self.get_path()
539 return self.srcnode().get_path()
545 try: return self.fs.stat(self.abspath)
546 except os.error: return None
550 return not self.stat() is None
554 return self.rfile().exists()
559 return self.stat()[stat.ST_MTIME]
566 return self.stat()[stat.ST_SIZE]
572 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
576 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
578 if hasattr(os, 'symlink'):
580 try: st = self.fs.lstat(self.abspath)
581 except os.error: return 0
582 return stat.S_ISLNK(st[stat.ST_MODE])
585 return 0 # no symlinks
587 def is_under(self, dir):
591 return self.dir.is_under(dir)
597 """If this node is in a build path, return the node
598 corresponding to its source file. Otherwise, return
605 srcnode = self.fs.Entry(name, dir.srcdir,
606 klass=self.__class__)
608 name = dir.name + os.sep + name
612 def get_path(self, dir=None):
613 """Return path relative to the current working directory of the
614 Node.FS.Base object that owns us."""
616 dir = self.fs.getcwd()
619 path_elems = self.path_elements
620 try: i = path_elems.index(dir)
621 except ValueError: pass
622 else: path_elems = path_elems[i+1:]
623 path_elems = map(lambda n: n.name, path_elems)
624 return string.join(path_elems, os.sep)
626 def set_src_builder(self, builder):
627 """Set the source code builder for this node."""
628 self.sbuilder = builder
629 if not self.has_builder():
630 self.builder_set(builder)
632 def src_builder(self):
633 """Fetch the source code builder for this node.
635 If there isn't one, we cache the source code builder specified
636 for the directory (which in turn will cache the value from its
637 parent directory, and so on up to the file system root).
641 except AttributeError:
642 scb = self.dir.src_builder()
646 def get_abspath(self):
647 """Get the absolute path of the file."""
650 def for_signature(self):
651 # Return just our name. Even an absolute path would not work,
652 # because that can change thanks to symlinks or remapped network
656 def get_subst_proxy(self):
659 except AttributeError:
660 ret = EntryProxy(self)
665 """This is the class for generic Node.FS entries--that is, things
666 that could be a File or a Dir, but we're just not sure yet.
667 Consequently, the methods in this class really exist just to
668 transform their associated object into the right class when the
669 time comes, and then call the same-named method in the transformed
672 def diskcheck_match(self):
675 def disambiguate(self):
680 self.__class__ = File
686 """We're a generic Entry, but the caller is actually looking for
687 a File at this point, so morph into one."""
688 self.__class__ = File
691 return File.rfile(self)
693 def get_found_includes(self, env, scanner, path):
694 """If we're looking for included files, it's because this Entry
695 is really supposed to be a File itself."""
696 return self.disambiguate().get_found_includes(env, scanner, path)
698 def scanner_key(self):
699 return self.get_suffix()
701 def get_contents(self):
702 """Fetch the contents of the entry.
704 Since this should return the real contents from the file
705 system, we check to see into what sort of subclass we should
708 self.__class__ = File
710 return self.get_contents()
714 return self.get_contents()
716 return '' # avoid errors for dangling symlinks
719 def rel_path(self, other):
720 return self.disambiguate().rel_path(other)
723 """Return if the Entry exists. Check the file system to see
724 what we should turn into first. Assume a file if there's no
726 return self.disambiguate().exists()
728 def calc_signature(self, calc=None):
729 """Return the Entry's calculated signature. Check the file
730 system to see what we should turn into first. Assume a file if
731 there's no directory."""
732 return self.disambiguate().calc_signature(calc)
734 def must_be_a_Dir(self):
735 """Called to make sure a Node is a Dir. Since we're an
736 Entry, we can morph into one."""
741 # This is for later so we can differentiate between Entry the class and Entry
742 # the method of the FS class.
748 if SCons.Memoize.use_memoizer:
749 __metaclass__ = SCons.Memoize.Memoized_Metaclass
751 # This class implements an abstraction layer for operations involving
752 # a local file system. Essentially, this wraps any function in
753 # the os, os.path or shutil modules that we use to actually go do
754 # anything with or to the local file system.
756 # Note that there's a very good chance we'll refactor this part of
757 # the architecture in some way as we really implement the interface(s)
758 # for remote file system Nodes. For example, the right architecture
759 # might be to have this be a subclass instead of a base class.
760 # Nevertheless, we're using this as a first step in that direction.
762 # We're not using chdir() yet because the calling subclass method
763 # needs to use os.chdir() directly to avoid recursion. Will we
764 # really need this one?
765 #def chdir(self, path):
766 # return os.chdir(path)
767 def chmod(self, path, mode):
768 return os.chmod(path, mode)
769 def copy2(self, src, dst):
770 return shutil.copy2(src, dst)
771 def exists(self, path):
772 return os.path.exists(path)
773 def getmtime(self, path):
774 return os.path.getmtime(path)
775 def getsize(self, path):
776 return os.path.getsize(path)
777 def isdir(self, path):
778 return os.path.isdir(path)
779 def isfile(self, path):
780 return os.path.isfile(path)
781 def link(self, src, dst):
782 return os.link(src, dst)
783 def lstat(self, path):
784 return os.lstat(path)
785 def listdir(self, path):
786 return os.listdir(path)
787 def makedirs(self, path):
788 return os.makedirs(path)
789 def mkdir(self, path):
790 return os.mkdir(path)
791 def rename(self, old, new):
792 return os.rename(old, new)
793 def stat(self, path):
795 def symlink(self, src, dst):
796 return os.symlink(src, dst)
797 def open(self, path):
799 def unlink(self, path):
800 return os.unlink(path)
802 if hasattr(os, 'symlink'):
803 def islink(self, path):
804 return os.path.islink(path)
806 def islink(self, path):
807 return 0 # no symlinks
809 if SCons.Memoize.use_old_memoization():
811 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
812 def __init__(self, *args, **kw):
813 apply(_FSBase.__init__, (self,)+args, kw)
814 SCons.Memoize.Memoizer.__init__(self)
818 # # Skeleton for the obvious methods we might need from the
819 # # abstraction layer for a remote filesystem.
820 # def upload(self, local_src, remote_dst):
822 # def download(self, remote_src, local_dst):
828 def __init__(self, path = None):
829 """Initialize the Node.FS subsystem.
831 The supplied path is the top of the source tree, where we
832 expect to find the top-level build file. If no path is
833 supplied, the current directory is the default.
835 The path argument must be a valid absolute path.
837 if __debug__: logInstanceCreation(self, 'Node.FS')
839 self.SConstruct_dir = None
840 self.CachePath = None
841 self.cache_force = None
842 self.cache_show = None
843 self.max_drift = default_max_drift
846 self.pathTop = os.getcwd()
850 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
855 def clear_cache(self):
859 def set_SConstruct_dir(self, dir):
860 self.SConstruct_dir = dir
862 def get_max_drift(self):
863 return self.max_drift
865 def set_max_drift(self, max_drift):
866 self.max_drift = max_drift
871 def __checkClass(self, node, klass):
872 if isinstance(node, klass) or klass == Entry:
874 if node.__class__ == Entry:
875 node.__class__ = klass
878 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
879 (node.__class__.__name__, node.path, klass.__name__)
881 def _doLookup(self, fsclass, name, directory = None, create = 1):
882 """This method differs from the File and Dir factory methods in
883 one important way: the meaning of the directory parameter.
884 In this method, if directory is None or not supplied, the supplied
885 name is expected to be an absolute path. If you try to look up a
886 relative path with directory=None, then an AssertionError will be
891 # This is a stupid hack to compensate for the fact that
892 # the POSIX and Win32 versions of os.path.normpath() behave
893 # differently in older versions of Python. In particular,
895 # os.path.normpath('./') == '.'
897 # os.path.normpath('./') == ''
898 # os.path.normpath('.\\') == ''
900 # This is a definite bug in the Python library, but we have
903 path_orig = string.split(name, os.sep)
904 path_norm = string.split(_my_normcase(name), os.sep)
906 first_orig = path_orig.pop(0) # strip first element
907 first_norm = path_norm.pop(0) # strip first element
909 drive, path_first = os.path.splitdrive(first_orig)
911 path_orig = [ path_first, ] + path_orig
912 path_norm = [ _my_normcase(path_first), ] + path_norm
914 drive = _my_normcase(drive)
917 directory = self.Root[drive]
920 raise SCons.Errors.UserError
921 directory = RootDir(drive, self)
922 self.Root[drive] = directory
927 last_orig = path_orig.pop() # strip last element
928 last_norm = path_norm.pop() # strip last element
930 # Lookup the directory
931 for orig, norm in map(None, path_orig, path_norm):
933 entries = directory.entries
934 except AttributeError:
935 # We tried to look up the entry in either an Entry or
936 # a File. Give whatever it is a chance to do what's
937 # appropriate: morph into a Dir or raise an exception.
938 directory.must_be_a_Dir()
939 entries = directory.entries
941 directory = entries[norm]
944 raise SCons.Errors.UserError
946 d = Dir(orig, directory, self)
948 # Check the file system (or not, as configured) to make
949 # sure there isn't already a file there.
952 directory.entries[norm] = d
953 directory.add_wkid(d)
956 directory.must_be_a_Dir()
959 e = directory.entries[last_norm]
962 raise SCons.Errors.UserError
964 result = fsclass(last_orig, directory, self)
966 # Check the file system (or not, as configured) to make
967 # sure there isn't already a directory at the path on
968 # disk where we just created a File node, and vice versa.
969 result.diskcheck_match()
971 directory.entries[last_norm] = result
972 directory.add_wkid(result)
974 result = self.__checkClass(e, fsclass)
977 def _transformPath(self, name, directory):
978 """Take care of setting up the correct top-level directory,
979 usually in preparation for a call to doLookup().
981 If the path name is prepended with a '#', then it is unconditionally
982 interpreted as relative to the top-level directory of this FS.
984 If directory is None, and name is a relative path,
985 then the same applies.
987 if name and name[0] == '#':
990 if name and (name[0] == os.sep or name[0] == '/'):
991 # Correct such that '#/foo' is equivalent
994 name = os.path.join('.', os.path.normpath(name))
996 directory = self._cwd
997 return (os.path.normpath(name), directory)
999 def chdir(self, dir, change_os_dir=0):
1000 """Change the current working directory for lookups.
1001 If change_os_dir is true, we will also change the "real" cwd
1009 os.chdir(dir.abspath)
1014 def Entry(self, name, directory = None, create = 1, klass=None):
1015 """Lookup or create a generic Entry node with the specified name.
1016 If the name is a relative path (begins with ./, ../, or a file
1017 name), then it is looked up relative to the supplied directory
1018 node, or to the top level directory of the FS (supplied at
1019 construction time) if no directory is supplied.
1025 if isinstance(name, Base):
1026 return self.__checkClass(name, klass)
1028 if directory and not isinstance(directory, Dir):
1029 directory = self.Dir(directory)
1030 name, directory = self._transformPath(name, directory)
1031 return self._doLookup(klass, name, directory, create)
1033 def File(self, name, directory = None, create = 1):
1034 """Lookup or create a File node with the specified name. If
1035 the name is a relative path (begins with ./, ../, or a file name),
1036 then it is looked up relative to the supplied directory node,
1037 or to the top level directory of the FS (supplied at construction
1038 time) if no directory is supplied.
1040 This method will raise TypeError if a directory is found at the
1044 return self.Entry(name, directory, create, File)
1046 def Dir(self, name, directory = None, create = 1):
1047 """Lookup or create a Dir node with the specified name. If
1048 the name is a relative path (begins with ./, ../, or a file name),
1049 then it is looked up relative to the supplied directory node,
1050 or to the top level directory of the FS (supplied at construction
1051 time) if no directory is supplied.
1053 This method will raise TypeError if a normal file is found at the
1057 return self.Entry(name, directory, create, Dir)
1059 def BuildDir(self, build_dir, src_dir, duplicate=1):
1060 """Link the supplied build directory to the source directory
1061 for purposes of building files."""
1063 if not isinstance(src_dir, SCons.Node.Node):
1064 src_dir = self.Dir(src_dir)
1065 if not isinstance(build_dir, SCons.Node.Node):
1066 build_dir = self.Dir(build_dir)
1067 if src_dir.is_under(build_dir):
1068 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1069 if build_dir.srcdir:
1070 if build_dir.srcdir == src_dir:
1071 return # We already did this.
1072 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1073 build_dir.link(src_dir, duplicate)
1075 def Repository(self, *dirs):
1076 """Specify Repository directories to search."""
1078 if not isinstance(d, SCons.Node.Node):
1080 self.Top.addRepository(d)
1082 def Rfindalldirs(self, pathlist, cwd):
1084 if SCons.Util.is_String(pathlist):
1085 pathlist = string.split(pathlist, os.pathsep)
1086 if not SCons.Util.is_List(pathlist):
1087 pathlist = [pathlist]
1089 for path in filter(None, pathlist):
1090 if isinstance(path, SCons.Node.Node):
1093 path, dir = self._transformPath(path, cwd)
1095 result.extend(dir.get_all_rdirs())
1098 def CacheDir(self, path):
1099 self.CachePath = path
1101 def build_dir_target_climb(self, orig, dir, tail):
1102 """Create targets in corresponding build directories
1104 Climb the directory tree, and look up path names
1105 relative to any linked build directories we find.
1110 fmt = "building associated BuildDir targets: %s"
1113 for bd in dir.build_dirs:
1114 if start_dir.is_under(bd):
1115 # If already in the build-dir location, don't reflect
1116 return [orig], fmt % str(orig)
1117 p = apply(os.path.join, [bd.path] + tail)
1118 targets.append(self.Entry(p))
1119 tail = [dir.name] + tail
1122 message = fmt % string.join(map(str, targets))
1123 return targets, message
1126 """A class for directories in a file system.
1129 def __init__(self, name, directory, fs):
1130 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1131 Base.__init__(self, name, directory, fs)
1135 """Turn a file system Node (either a freshly initialized directory
1136 object or a separate Entry object) into a proper directory object.
1138 Set up this directory's entries and hook it into the file
1139 system tree. Specify that directories (this Node) don't use
1140 signatures for calculating whether they're current.
1143 self.repositories = []
1147 self.entries['.'] = self
1148 self.entries['..'] = self.dir
1151 self._sconsign = None
1152 self.build_dirs = []
1154 # Don't just reset the executor, replace its action list,
1155 # because it might have some pre-or post-actions that need to
1157 self.builder = get_MkdirBuilder()
1158 self.get_executor().set_action_list(self.builder.action)
1160 def diskcheck_match(self):
1161 diskcheck_match(self, self.fs.isfile,
1162 "File %s found where directory expected.")
1164 def disambiguate(self):
1167 def __clearRepositoryCache(self, duplicate=None):
1168 """Called when we change the repository(ies) for a directory.
1169 This clears any cached information that is invalidated by changing
1172 for node in self.entries.values():
1173 if node != self.dir:
1174 if node != self and isinstance(node, Dir):
1175 node.__clearRepositoryCache(duplicate)
1180 except AttributeError:
1182 if duplicate != None:
1183 node.duplicate=duplicate
1185 def __resetDuplicate(self, node):
1187 node.duplicate = node.get_dir().duplicate
1189 def Entry(self, name):
1190 """Create an entry node named 'name' relative to this directory."""
1191 return self.fs.Entry(name, self)
1193 def Dir(self, name):
1194 """Create a directory node named 'name' relative to this directory."""
1195 return self.fs.Dir(name, self)
1197 def File(self, name):
1198 """Create a file node named 'name' relative to this directory."""
1199 return self.fs.File(name, self)
1201 def link(self, srcdir, duplicate):
1202 """Set this directory as the build directory for the
1203 supplied source directory."""
1204 self.srcdir = srcdir
1205 self.duplicate = duplicate
1206 self.__clearRepositoryCache(duplicate)
1207 srcdir.build_dirs.append(self)
1209 def getRepositories(self):
1210 """Returns a list of repositories for this directory.
1212 if self.srcdir and not self.duplicate:
1213 return self.srcdir.get_all_rdirs() + self.repositories
1214 return self.repositories
1216 def get_all_rdirs(self):
1222 for rep in dir.getRepositories():
1223 result.append(rep.Dir(fname))
1224 fname = dir.name + os.sep + fname
1228 def addRepository(self, dir):
1229 if dir != self and not dir in self.repositories:
1230 self.repositories.append(dir)
1232 self.__clearRepositoryCache()
1235 return self.entries['..']
1237 def rel_path(self, other):
1238 """Return a path to "other" relative to this directory.
1240 if isinstance(other, Dir):
1246 except AttributeError:
1249 return name and name[0] or '.'
1251 for x, y in map(None, self.path_elements, other.path_elements):
1255 path_elems = ['..']*(len(self.path_elements)-i) \
1256 + map(lambda n: n.name, other.path_elements[i:]) \
1259 return string.join(path_elems, os.sep)
1262 if not self.implicit is None:
1265 self.implicit_dict = {}
1266 self._children_reset()
1268 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1269 deps = filter(dont_scan, self.entries.keys())
1270 # keys() is going to give back the entries in an internal,
1271 # unsorted order. Sort 'em so the order is deterministic.
1273 entries = map(lambda n, e=self.entries: e[n], deps)
1275 self._add_child(self.implicit, self.implicit_dict, entries)
1277 def get_found_includes(self, env, scanner, path):
1278 """Return the included implicit dependencies in this file.
1279 Cache results so we only scan the file once per path
1280 regardless of how many times this information is requested.
1284 # Clear cached info for this Node. If we already visited this
1285 # directory on our walk down the tree (because we didn't know at
1286 # that point it was being used as the source for another Node)
1287 # then we may have calculated build signature before realizing
1288 # we had to scan the disk. Now that we have to, though, we need
1289 # to invalidate the old calculated signature so that any node
1290 # dependent on our directory structure gets one that includes
1291 # info about everything on disk.
1293 return scanner(self, env, path)
1295 def build(self, **kw):
1296 """A null "builder" for directories."""
1298 if not self.builder is MkdirBuilder:
1299 apply(SCons.Node.Node.build, [self,], kw)
1302 """Create this directory, silently and without worrying about
1303 whether the builder is the default or not."""
1309 listDirs.append(parent)
1312 raise SCons.Errors.StopError, parent.path
1315 for dirnode in listDirs:
1317 # Don't call dirnode.build(), call the base Node method
1318 # directly because we definitely *must* create this
1319 # directory. The dirnode.build() method will suppress
1320 # the build if it's the default builder.
1321 SCons.Node.Node.build(dirnode)
1322 dirnode.get_executor().nullify()
1323 # The build() action may or may not have actually
1324 # created the directory, depending on whether the -n
1325 # option was used or not. Delete the _exists and
1326 # _rexists attributes so they can be reevaluated.
1331 def multiple_side_effect_has_builder(self):
1333 return not self.builder is MkdirBuilder and self.has_builder()
1335 def alter_targets(self):
1336 """Return any corresponding targets in a build directory.
1338 return self.fs.build_dir_target_climb(self, self, [])
1340 def scanner_key(self):
1341 """A directory does not get scanned."""
1344 def get_contents(self):
1345 """Return aggregate contents of all our children."""
1346 contents = cStringIO.StringIO()
1347 for kid in self.children():
1348 contents.write(kid.get_contents())
1349 return contents.getvalue()
1354 def do_duplicate(self, src):
1357 def current(self, calc=None):
1358 """If all of our children were up-to-date, then this
1359 directory was up-to-date, too."""
1360 if not self.builder is MkdirBuilder and not self.exists():
1363 for kid in self.children():
1365 if s and (not state or s > state):
1368 if state == 0 or state == SCons.Node.up_to_date:
1375 if not self.exists():
1376 norm_name = _my_normcase(self.name)
1377 for dir in self.dir.get_all_rdirs():
1378 try: node = dir.entries[norm_name]
1379 except KeyError: node = dir.dir_on_disk(self.name)
1380 if node and node.exists() and \
1381 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1386 """Return the .sconsign file info for this directory,
1387 creating it first if necessary."""
1388 if not self._sconsign:
1389 import SCons.SConsign
1390 self._sconsign = SCons.SConsign.ForDirectory(self)
1391 return self._sconsign
1394 """Dir has a special need for srcnode()...if we
1395 have a srcdir attribute set, then that *is* our srcnode."""
1398 return Base.srcnode(self)
1400 def get_timestamp(self):
1401 """Return the latest timestamp from among our children"""
1403 for kid in self.children():
1404 if kid.get_timestamp() > stamp:
1405 stamp = kid.get_timestamp()
1408 def entry_abspath(self, name):
1409 return self.abspath + os.sep + name
1411 def entry_path(self, name):
1412 return self.path + os.sep + name
1414 def entry_tpath(self, name):
1415 return self.tpath + os.sep + name
1417 def must_be_a_Dir(self):
1418 """Called to make sure a Node is a Dir. Since we're already
1419 one, this is a no-op for us."""
1422 def entry_exists_on_disk(self, name):
1424 return self.fs.exists(self.entry_abspath(name))
1426 def srcdir_list(self):
1434 d = dir.srcdir.Dir(dirname)
1436 # Shouldn't source from something in the build path:
1437 # build_dir is probably under src_dir, in which case
1438 # we are reflecting.
1441 dirname = dir.name + os.sep + dirname
1446 def srcdir_duplicate(self, name):
1447 for dir in self.srcdir_list():
1448 if dir.entry_exists_on_disk(name):
1449 srcnode = dir.File(name)
1451 node = self.File(name)
1452 node.do_duplicate(srcnode)
1458 def srcdir_find_file(self, filename):
1461 if (isinstance(node, File) or isinstance(node, Entry)) and \
1462 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1466 norm_name = _my_normcase(filename)
1468 for rdir in self.get_all_rdirs():
1469 try: node = rdir.entries[norm_name]
1470 except KeyError: node = rdir.file_on_disk(filename)
1471 else: node = func(node)
1475 for srcdir in self.srcdir_list():
1476 for rdir in srcdir.get_all_rdirs():
1477 try: node = rdir.entries[norm_name]
1478 except KeyError: node = rdir.file_on_disk(filename)
1479 else: node = func(node)
1481 return File(filename, self, self.fs), srcdir
1485 def dir_on_disk(self, name):
1486 if self.entry_exists_on_disk(name):
1487 try: return self.Dir(name)
1488 except TypeError: pass
1491 def file_on_disk(self, name):
1492 if self.entry_exists_on_disk(name) or \
1493 diskcheck_rcs(self, name) or \
1494 diskcheck_sccs(self, name):
1495 try: return self.File(name)
1496 except TypeError: pass
1497 return self.srcdir_duplicate(name)
1500 """A class for the root directory of a file system.
1502 This is the same as a Dir class, except that the path separator
1503 ('/' or '\\') is actually part of the name, so we don't need to
1504 add a separator when creating the path names of entries within
1507 def __init__(self, name, fs):
1508 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1509 # We're going to be our own parent directory (".." entry and .dir
1510 # attribute) so we have to set up some values so Base.__init__()
1511 # won't gag won't it calls some of our methods.
1515 self.path_elements = []
1517 Base.__init__(self, name, self, fs)
1519 # Now set our paths to what we really want them to be: the
1520 # initial drive letter (the name) plus the directory separator.
1521 self.abspath = name + os.sep
1522 self.path = name + os.sep
1523 self.tpath = name + os.sep
1529 def entry_abspath(self, name):
1530 return self.abspath + name
1532 def entry_path(self, name):
1533 return self.path + name
1535 def entry_tpath(self, name):
1536 return self.tpath + name
1538 def is_under(self, dir):
1550 def src_builder(self):
1553 class NodeInfo(SCons.Node.NodeInfo):
1554 # The bsig attributes needs to stay here, if it's initialized in
1555 # __init__() then the assignment seems to overwrite any values
1556 # unpickled from .sconsign files.
1558 def __cmp__(self, other):
1559 return cmp(self.bsig, other.bsig)
1560 def update(self, node):
1561 self.timestamp = node.get_timestamp()
1562 self.size = node.getsize()
1564 class BuildInfo(SCons.Node.BuildInfo):
1565 def __init__(self, node):
1566 SCons.Node.BuildInfo.__init__(self, node)
1568 def convert_to_sconsign(self):
1569 """Convert this BuildInfo object for writing to a .sconsign file
1571 We hung onto the node that we refer to so that we can translate
1572 the lists of bsources, bdepends and bimplicit Nodes into strings
1573 relative to the node, but we don't want to write out that Node
1574 itself to the .sconsign file, so we delete the attribute in
1577 rel_path = self.node.rel_path
1578 delattr(self, 'node')
1579 for attr in ['bsources', 'bdepends', 'bimplicit']:
1581 val = getattr(self, attr)
1582 except AttributeError:
1585 setattr(self, attr, map(rel_path, val))
1586 def convert_from_sconsign(self, dir, name):
1587 """Convert a newly-read BuildInfo object for in-SCons use
1589 An on-disk BuildInfo comes without a reference to the node
1590 for which it's intended, so we have to convert the arguments
1591 and add back a self.node attribute. The bsources, bdepends and
1592 bimplicit lists all come from disk as paths relative to that node,
1593 so convert them to actual Nodes for use by the rest of SCons.
1595 self.node = dir.Entry(name)
1596 Entry_func = self.node.dir.Entry
1597 for attr in ['bsources', 'bdepends', 'bimplicit']:
1599 val = getattr(self, attr)
1600 except AttributeError:
1603 setattr(self, attr, map(Entry_func, val))
1606 """A class for files in a file system.
1608 def diskcheck_match(self):
1609 diskcheck_match(self, self.fs.isdir,
1610 "Directory %s found where file expected.")
1612 def __init__(self, name, directory, fs):
1613 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1614 Base.__init__(self, name, directory, fs)
1617 def Entry(self, name):
1618 """Create an entry node named 'name' relative to
1619 the SConscript directory of this file."""
1620 return self.fs.Entry(name, self.cwd)
1622 def Dir(self, name):
1623 """Create a directory node named 'name' relative to
1624 the SConscript directory of this file."""
1625 return self.fs.Dir(name, self.cwd)
1627 def Dirs(self, pathlist):
1628 """Create a list of directories relative to the SConscript
1629 directory of this file."""
1630 return map(lambda p, s=self: s.Dir(p), pathlist)
1632 def File(self, name):
1633 """Create a file node named 'name' relative to
1634 the SConscript directory of this file."""
1635 return self.fs.File(name, self.cwd)
1637 def RDirs(self, pathlist):
1638 """Search for a list of directories in the Repository list."""
1639 return self.fs.Rfindalldirs(pathlist, self.cwd)
1642 """Turn a file system node into a File object. __cache_reset__"""
1643 self.scanner_paths = {}
1644 if not hasattr(self, '_local'):
1647 def disambiguate(self):
1650 def scanner_key(self):
1651 return self.get_suffix()
1653 def get_contents(self):
1654 if not self.rexists():
1656 return open(self.rfile().abspath, "rb").read()
1658 def get_timestamp(self):
1660 return self.rfile().getmtime()
1664 def store_info(self, obj):
1665 # Merge our build information into the already-stored entry.
1666 # This accomodates "chained builds" where a file that's a target
1667 # in one build (SConstruct file) is a source in a different build.
1668 # See test/chained-build.py for the use case.
1669 entry = self.get_stored_info()
1671 self.dir.sconsign().set_entry(self.name, entry)
1673 def get_stored_info(self):
1676 stored = self.dir.sconsign().get_entry(self.name)
1677 except (KeyError, OSError):
1678 return self.new_binfo()
1680 if not hasattr(stored, 'ninfo'):
1681 # Transition: The .sconsign file entry has no NodeInfo
1682 # object, which means it's a slightly older BuildInfo.
1683 # Copy over the relevant attributes.
1684 ninfo = stored.ninfo = self.new_ninfo()
1685 for attr in ninfo.__dict__.keys():
1687 setattr(ninfo, attr, getattr(stored, attr))
1688 except AttributeError:
1692 def get_stored_implicit(self):
1693 binfo = self.get_stored_info()
1694 try: return binfo.bimplicit
1695 except AttributeError: return None
1697 def rel_path(self, other):
1698 return self.dir.rel_path(other)
1700 def get_found_includes(self, env, scanner, path):
1701 """Return the included implicit dependencies in this file.
1702 Cache results so we only scan the file once per path
1703 regardless of how many times this information is requested.
1707 return scanner(self, env, path)
1709 def _createDir(self):
1710 # ensure that the directories for this node are
1714 def retrieve_from_cache(self):
1715 """Try to retrieve the node's content from a cache
1717 This method is called from multiple threads in a parallel build,
1718 so only do thread safe stuff here. Do thread unsafe stuff in
1721 Note that there's a special trick here with the execute flag
1722 (one that's not normally done for other actions). Basically
1723 if the user requested a noexec (-n) build, then
1724 SCons.Action.execute_actions is set to 0 and when any action
1725 is called, it does its showing but then just returns zero
1726 instead of actually calling the action execution operation.
1727 The problem for caching is that if the file does NOT exist in
1728 cache then the CacheRetrieveString won't return anything to
1729 show for the task, but the Action.__call__ won't call
1730 CacheRetrieveFunc; instead it just returns zero, which makes
1731 the code below think that the file *was* successfully
1732 retrieved from the cache, therefore it doesn't do any
1733 subsequent building. However, the CacheRetrieveString didn't
1734 print anything because it didn't actually exist in the cache,
1735 and no more build actions will be performed, so the user just
1736 sees nothing. The fix is to tell Action.__call__ to always
1737 execute the CacheRetrieveFunc and then have the latter
1738 explicitly check SCons.Action.execute_actions itself.
1740 Returns true iff the node was successfully retrieved.
1742 b = self.is_derived()
1743 if not b and not self.has_src_builder():
1745 if b and self.fs.CachePath:
1746 if self.fs.cache_show:
1747 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1748 self.build(presub=0, execute=0)
1750 elif CacheRetrieve(self, [], None, execute=1) == 0:
1755 """Called just after this node is successfully built.
1757 # Push this file out to cache before the superclass Node.built()
1758 # method has a chance to clear the build signature, which it
1759 # will do if this file has a source scanner.
1760 if self.fs.CachePath and self.exists():
1761 CachePush(self, [], None)
1762 self.fs.clear_cache()
1763 SCons.Node.Node.built(self)
1766 if self.fs.CachePath and self.fs.cache_force and self.exists():
1767 CachePush(self, None, None)
1769 def has_src_builder(self):
1770 """Return whether this Node has a source builder or not.
1772 If this Node doesn't have an explicit source code builder, this
1773 is where we figure out, on the fly, if there's a transparent
1774 source code builder for it.
1776 Note that if we found a source builder, we also set the
1777 self.builder attribute, so that all of the methods that actually
1778 *build* this file don't have to do anything different.
1782 except AttributeError:
1786 scb = self.dir.src_builder()
1788 if diskcheck_sccs(self.dir, self.name):
1789 scb = get_DefaultSCCSBuilder()
1790 elif diskcheck_rcs(self.dir, self.name):
1791 scb = get_DefaultRCSBuilder()
1795 self.builder_set(scb)
1797 return not scb is None
1799 def alter_targets(self):
1800 """Return any corresponding targets in a build directory.
1802 if self.is_derived():
1804 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1806 def is_pseudo_derived(self):
1808 return self.has_src_builder()
1810 def _rmv_existing(self):
1812 Unlink(self, [], None)
1815 """Prepare for this file to be created."""
1816 SCons.Node.Node.prepare(self)
1818 if self.get_state() != SCons.Node.up_to_date:
1820 if self.is_derived() and not self.precious:
1821 self._rmv_existing()
1825 except SCons.Errors.StopError, drive:
1826 desc = "No drive `%s' for target `%s'." % (drive, self)
1827 raise SCons.Errors.StopError, desc
1830 """Remove this file."""
1831 if self.exists() or self.islink():
1832 self.fs.unlink(self.path)
1836 def do_duplicate(self, src):
1839 Unlink(self, None, None)
1840 except SCons.Errors.BuildError:
1843 Link(self, src, None)
1844 except SCons.Errors.BuildError, e:
1845 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1846 raise SCons.Errors.StopError, desc
1848 # The Link() action may or may not have actually
1849 # created the file, depending on whether the -n
1850 # option was used or not. Delete the _exists and
1851 # _rexists attributes so they can be reevaluated.
1856 # Duplicate from source path if we are set up to do this.
1857 if self.duplicate and not self.is_derived() and not self.linked:
1860 return Base.exists(self)
1862 if src.abspath != self.abspath and src.exists():
1863 self.do_duplicate(src)
1864 return Base.exists(self)
1867 # SIGNATURE SUBSYSTEM
1870 def new_binfo(self):
1871 return BuildInfo(self)
1873 def new_ninfo(self):
1878 def get_csig(self, calc=None):
1880 Generate a node's content signature, the digested signature
1884 cache - alternate node to use for the signature cache
1885 returns - the content signature
1888 return self.binfo.ninfo.csig
1889 except AttributeError:
1893 calc = self.calculator()
1895 max_drift = self.fs.max_drift
1896 mtime = self.get_timestamp()
1897 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
1901 old = self.get_stored_info().ninfo
1903 if old.timestamp and old.csig and old.timestamp == mtime:
1905 except AttributeError:
1908 csig = calc.module.signature(self)
1910 binfo = self.get_binfo()
1916 self.store_info(binfo)
1924 def current(self, calc=None):
1925 self.binfo = self.gen_binfo(calc)
1929 if self.always_build:
1931 if not self.exists():
1932 # The file doesn't exist locally...
1935 # ...but there is one in a Repository...
1936 old = r.get_stored_info()
1937 new = self.get_binfo()
1939 # ...and it's even up-to-date...
1941 # ...and they'd like a local copy.
1942 LocalCopy(self, r, None)
1943 self.store_info(new)
1947 old = self.get_stored_info()
1948 new = self.get_binfo()
1953 if not self.exists():
1954 norm_name = _my_normcase(self.name)
1955 for dir in self.dir.get_all_rdirs():
1956 try: node = dir.entries[norm_name]
1957 except KeyError: node = dir.file_on_disk(self.name)
1958 if node and node.exists() and \
1959 (isinstance(node, File) or isinstance(node, Entry) \
1960 or not node.is_derived()):
1965 return str(self.rfile())
1967 def cachepath(self):
1968 if not self.fs.CachePath:
1970 ninfo = self.get_binfo().ninfo
1971 if not hasattr(ninfo, 'bsig'):
1972 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
1973 elif ninfo.bsig is None:
1974 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1975 # Add the path to the cache signature, because multiple
1976 # targets built by the same action will all have the same
1977 # build signature, and we have to differentiate them somehow.
1978 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
1979 subdir = string.upper(cache_sig[0])
1980 dir = os.path.join(self.fs.CachePath, subdir)
1981 return dir, os.path.join(dir, cache_sig)
1983 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1984 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1986 def must_be_a_Dir(self):
1987 """Called to make sure a Node is a Dir. Since we're already a
1988 File, this is a TypeError..."""
1989 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1993 def find_file(filename, paths, verbose=None):
1995 find_file(str, [Dir()]) -> [nodes]
1997 filename - a filename to find
1998 paths - a list of directory path *nodes* to search in. Can be
1999 represented as a list, a tuple, or a callable that is
2000 called with no arguments and returns the list or tuple.
2002 returns - the node created from the found file.
2004 Find a node corresponding to either a derived file or a file
2005 that exists already.
2007 Only the first file found is returned, and none is returned
2008 if no file is found.
2012 if not SCons.Util.is_String(verbose):
2013 verbose = "find_file"
2014 if not callable(verbose):
2015 verbose = ' %s: ' % verbose
2016 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2018 verbose = lambda x: x
2023 # Give Entries a chance to morph into Dirs.
2024 paths = map(lambda p: p.must_be_a_Dir(), paths)
2026 filedir, filename = os.path.split(filename)
2028 def filedir_lookup(p, fd=filedir):
2032 # We tried to look up a Dir, but it seems there's already
2033 # a File (or something else) there. No big.
2035 paths = filter(None, map(filedir_lookup, paths))
2038 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2039 node, d = dir.srcdir_find_file(filename)
2041 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2045 def find_files(filenames, paths):
2047 find_files([str], [Dir()]) -> [nodes]
2049 filenames - a list of filenames to find
2050 paths - a list of directory path *nodes* to search in
2052 returns - the nodes created from the found files.
2054 Finds nodes corresponding to either derived files or files
2057 Only the first file found is returned for each filename,
2058 and any files that aren't found are ignored.
2060 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2061 return filter(None, nodes)