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
55 # The max_drift value: by default, use a cached signature value for
56 # any file that's been untouched for more than two days.
57 default_max_drift = 2*24*60*60
60 # We stringify these file system Nodes a lot. Turning a file system Node
61 # into a string is non-trivial, because the final string representation
62 # can depend on a lot of factors: whether it's a derived target or not,
63 # whether it's linked to a repository or source directory, and whether
64 # there's duplication going on. The normal technique for optimizing
65 # calculations like this is to memoize (cache) the string value, so you
66 # only have to do the calculation once.
68 # A number of the above factors, however, can be set after we've already
69 # been asked to return a string for a Node, because a Repository() or
70 # BuildDir() call or the like may not occur until later in SConscript
71 # files. So this variable controls whether we bother trying to save
72 # string values for Nodes. The wrapper interface can set this whenever
73 # they're done mucking with Repository and BuildDir and the other stuff,
74 # to let this module know it can start returning saved string values
79 def save_strings(val):
84 # SCons.Action objects for interacting with the outside world.
86 # The Node.FS methods in this module should use these actions to
87 # create and/or remove files and directories; they should *not* use
88 # os.{link,symlink,unlink,mkdir}(), etc., directly.
90 # Using these SCons.Action objects ensures that descriptions of these
91 # external activities are properly displayed, that the displays are
92 # suppressed when the -s (silent) option is used, and (most importantly)
93 # the actions are disabled when the the -n option is used, in which case
94 # there should be *no* changes to the external file system(s)...
97 def _copy_func(src, dest):
98 shutil.copy2(src, dest)
100 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
102 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
103 'hard-copy', 'soft-copy', 'copy']
105 Link_Funcs = [] # contains the callables of the specified duplication style
107 def set_duplicate(duplicate):
108 # Fill in the Link_Funcs list according to the argument
109 # (discarding those not available on the platform).
111 # Set up the dictionary that maps the argument names to the
112 # underlying implementations. We do this inside this function,
113 # not in the top-level module code, so that we can remap os.link
114 # and os.symlink for testing purposes.
116 _hardlink_func = os.link
117 except AttributeError:
118 _hardlink_func = None
121 _softlink_func = os.symlink
122 except AttributeError:
123 _softlink_func = None
126 'hard' : _hardlink_func,
127 'soft' : _softlink_func,
131 if not duplicate in Valid_Duplicates:
132 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
133 "should be in Valid_Duplicates")
136 for func in string.split(duplicate,'-'):
138 Link_Funcs.append(link_dict[func])
140 def LinkFunc(target, source, env):
141 # Relative paths cause problems with symbolic links, so
142 # we use absolute paths, which may be a problem for people
143 # who want to move their soft-linked src-trees around. Those
144 # people should use the 'hard-copy' mode, softlinks cannot be
145 # used for that; at least I have no idea how ...
146 src = source[0].abspath
147 dest = target[0].abspath
148 dir, file = os.path.split(dest)
149 if dir and not target[0].fs.isdir(dir):
152 # Set a default order of link functions.
153 set_duplicate('hard-soft-copy')
154 # Now link the files with the previously specified order.
155 for func in Link_Funcs:
160 if func == Link_Funcs[-1]:
161 # exception of the last link method (copy) are fatal
167 Link = SCons.Action.Action(LinkFunc, None)
168 def LocalString(target, source, env):
169 return 'Local copy of %s from %s' % (target[0], source[0])
171 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
173 def UnlinkFunc(target, source, env):
175 t.fs.unlink(t.abspath)
178 Unlink = SCons.Action.Action(UnlinkFunc, None)
180 def MkdirFunc(target, source, env):
183 t.fs.mkdir(t.abspath)
186 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
190 def get_MkdirBuilder():
192 if MkdirBuilder is None:
194 # "env" will get filled in by Executor.get_build_env()
195 # calling SCons.Defaults.DefaultEnvironment() when necessary.
196 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
200 name = "MkdirBuilder")
203 def CacheRetrieveFunc(target, source, env):
206 cachedir, cachefile = t.cachepath()
207 if fs.exists(cachefile):
208 if SCons.Action.execute_actions:
209 fs.copy2(cachefile, t.path)
210 st = fs.stat(cachefile)
211 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
215 def CacheRetrieveString(target, source, env):
217 cachedir, cachefile = t.cachepath()
218 if t.fs.exists(cachefile):
219 return "Retrieved `%s' from cache" % t.path
222 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
224 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
226 def CachePushFunc(target, source, env):
229 cachedir, cachefile = t.cachepath()
230 if fs.exists(cachefile):
231 # Don't bother copying it if it's already there.
234 if not fs.isdir(cachedir):
235 fs.makedirs(cachedir)
237 tempfile = cachefile+'.tmp'
239 fs.copy2(t.path, tempfile)
240 fs.rename(tempfile, cachefile)
242 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
244 # It's possible someone else tried writing the file at the same
245 # time we did. Print a warning but don't stop the build, since
246 # it doesn't affect the correctness of the build.
247 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
248 "Unable to copy %s to cache. Cache file is %s"
249 % (str(target), cachefile))
252 CachePush = SCons.Action.Action(CachePushFunc, None)
259 DefaultSCCSBuilder = None
260 DefaultRCSBuilder = None
262 def get_DefaultSCCSBuilder():
263 global DefaultSCCSBuilder
264 if DefaultSCCSBuilder is None:
266 # "env" will get filled in by Executor.get_build_env()
267 # calling SCons.Defaults.DefaultEnvironment() when necessary.
268 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
269 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
271 name = "DefaultSCCSBuilder")
272 return DefaultSCCSBuilder
274 def get_DefaultRCSBuilder():
275 global DefaultRCSBuilder
276 if DefaultRCSBuilder is None:
278 # "env" will get filled in by Executor.get_build_env()
279 # calling SCons.Defaults.DefaultEnvironment() when necessary.
280 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
281 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
283 name = "DefaultRCSBuilder")
284 return DefaultRCSBuilder
286 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
287 _is_cygwin = sys.platform == "cygwin"
288 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
293 return string.upper(x)
298 def __init__(self, type, do, ignore):
304 self.__call__ = self.do
305 def set_ignore(self):
306 self.__call__ = self.ignore
308 if self.type in list:
313 def do_diskcheck_match(node, predicate, errorfmt):
316 raise TypeError, errorfmt % path
318 def ignore_diskcheck_match(node, predicate, errorfmt):
321 def do_diskcheck_rcs(node, name):
322 rcspath = 'RCS' + os.sep + name+',v'
323 return node.entry_exists_on_disk(rcspath)
325 def ignore_diskcheck_rcs(node, name):
328 def do_diskcheck_sccs(node, name):
329 sccspath = 'SCCS' + os.sep + 's.'+name
330 return node.entry_exists_on_disk(sccspath)
332 def ignore_diskcheck_sccs(node, name):
335 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
336 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
337 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
345 def set_diskcheck(list):
346 for dc in diskcheckers:
349 def diskcheck_types():
350 return map(lambda dc: dc.type, diskcheckers)
354 class EntryProxy(SCons.Util.Proxy):
355 def __get_abspath(self):
357 return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
358 entry.name + "_abspath")
360 def __get_filebase(self):
361 name = self.get().name
362 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
365 def __get_suffix(self):
366 name = self.get().name
367 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
370 def __get_file(self):
371 name = self.get().name
372 return SCons.Util.SpecialAttrWrapper(name, name + "_file")
374 def __get_base_path(self):
375 """Return the file's directory and file name, with the
378 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
379 entry.name + "_base")
381 def __get_posix_path(self):
382 """Return the path with / as the path separator,
383 regardless of platform."""
388 r = string.replace(entry.get_path(), os.sep, '/')
389 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_posix")
391 def __get_win32_path(self):
392 """Return the path with \ as the path separator,
393 regardless of platform."""
398 r = string.replace(entry.get_path(), os.sep, '\\')
399 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_win32")
401 def __get_srcnode(self):
402 return EntryProxy(self.get().srcnode())
404 def __get_srcdir(self):
405 """Returns the directory containing the source node linked to this
406 node via BuildDir(), or the directory of this node if not linked."""
407 return EntryProxy(self.get().srcnode().dir)
409 def __get_rsrcnode(self):
410 return EntryProxy(self.get().srcnode().rfile())
412 def __get_rsrcdir(self):
413 """Returns the directory containing the source node linked to this
414 node via BuildDir(), or the directory of this node if not linked."""
415 return EntryProxy(self.get().srcnode().rfile().dir)
418 return EntryProxy(self.get().dir)
420 dictSpecialAttrs = { "base" : __get_base_path,
421 "posix" : __get_posix_path,
422 "win32" : __get_win32_path,
423 "srcpath" : __get_srcnode,
424 "srcdir" : __get_srcdir,
426 "abspath" : __get_abspath,
427 "filebase" : __get_filebase,
428 "suffix" : __get_suffix,
430 "rsrcpath" : __get_rsrcnode,
431 "rsrcdir" : __get_rsrcdir,
434 def __getattr__(self, name):
435 # This is how we implement the "special" attributes
436 # such as base, posix, srcdir, etc.
438 return self.dictSpecialAttrs[name](self)
441 attr = SCons.Util.Proxy.__getattr__(self, name)
442 except AttributeError:
444 classname = string.split(str(entry.__class__), '.')[-1]
445 if classname[-2:] == "'>":
446 # new-style classes report their name as:
447 # "<class 'something'>"
448 # instead of the classic classes:
450 classname = classname[:-2]
451 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
454 class Base(SCons.Node.Node):
455 """A generic class for file system entries. This class is for
456 when we don't know yet whether the entry being looked up is a file
457 or a directory. Instances of this class can morph into either
458 Dir or File objects by a later, more precise lookup.
460 Note: this class does not define __cmp__ and __hash__ for
461 efficiency reasons. SCons does a lot of comparing of
462 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
463 as fast as possible, which means we want to use Python's built-in
464 object identity comparisons.
467 def __init__(self, name, directory, fs):
468 """Initialize a generic Node.FS.Base object.
470 Call the superclass initialization, take care of setting up
471 our relative and absolute paths, identify our parent
472 directory, and indicate that this node should use
474 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
475 SCons.Node.Node.__init__(self)
480 assert directory, "A directory must be provided"
482 self.abspath = directory.entry_abspath(name)
483 if directory.path == '.':
486 self.path = directory.entry_path(name)
487 if directory.tpath == '.':
490 self.tpath = directory.entry_tpath(name)
491 self.path_elements = directory.path_elements + [self]
494 self.cwd = None # will hold the SConscript directory for target nodes
495 self.duplicate = directory.duplicate
498 """Completely clear a Node.FS.Base object of all its cached
499 state (so that it can be re-evaluated by interfaces that do
500 continuous integration builds).
503 SCons.Node.Node.clear(self)
508 def get_suffix(self):
510 return SCons.Util.splitext(self.name)[1]
516 """A Node.FS.Base object's string representation is its path
520 return self._save_str()
521 return self._get_str()
525 return self._get_str()
528 if self.duplicate or self.is_derived():
529 return self.get_path()
530 return self.srcnode().get_path()
536 try: return self.fs.stat(self.abspath)
537 except os.error: return None
541 return not self.stat() is None
545 return self.rfile().exists()
550 return self.stat()[stat.ST_MTIME]
557 return self.stat()[stat.ST_SIZE]
563 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
567 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
569 if hasattr(os, 'symlink'):
571 try: st = self.fs.lstat(self.abspath)
572 except os.error: return 0
573 return stat.S_ISLNK(st[stat.ST_MODE])
576 return 0 # no symlinks
578 def is_under(self, dir):
582 return self.dir.is_under(dir)
588 """If this node is in a build path, return the node
589 corresponding to its source file. Otherwise, return
596 srcnode = self.fs.Entry(name, dir.srcdir,
597 klass=self.__class__)
599 name = dir.name + os.sep + name
603 def get_path(self, dir=None):
604 """Return path relative to the current working directory of the
605 Node.FS.Base object that owns us."""
607 dir = self.fs.getcwd()
610 path_elems = self.path_elements
611 try: i = path_elems.index(dir)
612 except ValueError: pass
613 else: path_elems = path_elems[i+1:]
614 path_elems = map(lambda n: n.name, path_elems)
615 return string.join(path_elems, os.sep)
617 def set_src_builder(self, builder):
618 """Set the source code builder for this node."""
619 self.sbuilder = builder
620 if not self.has_builder():
621 self.builder_set(builder)
623 def src_builder(self):
624 """Fetch the source code builder for this node.
626 If there isn't one, we cache the source code builder specified
627 for the directory (which in turn will cache the value from its
628 parent directory, and so on up to the file system root).
632 except AttributeError:
633 scb = self.dir.src_builder()
637 def get_abspath(self):
638 """Get the absolute path of the file."""
641 def for_signature(self):
642 # Return just our name. Even an absolute path would not work,
643 # because that can change thanks to symlinks or remapped network
647 def get_subst_proxy(self):
650 except AttributeError:
651 ret = EntryProxy(self)
656 """This is the class for generic Node.FS entries--that is, things
657 that could be a File or a Dir, but we're just not sure yet.
658 Consequently, the methods in this class really exist just to
659 transform their associated object into the right class when the
660 time comes, and then call the same-named method in the transformed
663 def diskcheck_match(self):
666 def disambiguate(self):
671 self.__class__ = File
677 """We're a generic Entry, but the caller is actually looking for
678 a File at this point, so morph into one."""
679 self.__class__ = File
682 return File.rfile(self)
684 def get_found_includes(self, env, scanner, path):
685 """If we're looking for included files, it's because this Entry
686 is really supposed to be a File itself."""
687 return self.disambiguate().get_found_includes(env, scanner, path)
689 def scanner_key(self):
690 return self.get_suffix()
692 def get_contents(self):
693 """Fetch the contents of the entry.
695 Since this should return the real contents from the file
696 system, we check to see into what sort of subclass we should
699 self.__class__ = File
701 return self.get_contents()
705 return self.get_contents()
707 return '' # avoid errors for dangling symlinks
710 def rel_path(self, other):
711 return self.disambiguate().rel_path(other)
714 """Return if the Entry exists. Check the file system to see
715 what we should turn into first. Assume a file if there's no
717 return self.disambiguate().exists()
719 def calc_signature(self, calc=None):
720 """Return the Entry's calculated signature. Check the file
721 system to see what we should turn into first. Assume a file if
722 there's no directory."""
723 return self.disambiguate().calc_signature(calc)
725 def must_be_a_Dir(self):
726 """Called to make sure a Node is a Dir. Since we're an
727 Entry, we can morph into one."""
732 # This is for later so we can differentiate between Entry the class and Entry
733 # the method of the FS class.
739 if SCons.Memoize.use_memoizer:
740 __metaclass__ = SCons.Memoize.Memoized_Metaclass
742 # This class implements an abstraction layer for operations involving
743 # a local file system. Essentially, this wraps any function in
744 # the os, os.path or shutil modules that we use to actually go do
745 # anything with or to the local file system.
747 # Note that there's a very good chance we'll refactor this part of
748 # the architecture in some way as we really implement the interface(s)
749 # for remote file system Nodes. For example, the right architecture
750 # might be to have this be a subclass instead of a base class.
751 # Nevertheless, we're using this as a first step in that direction.
753 # We're not using chdir() yet because the calling subclass method
754 # needs to use os.chdir() directly to avoid recursion. Will we
755 # really need this one?
756 #def chdir(self, path):
757 # return os.chdir(path)
758 def chmod(self, path, mode):
759 return os.chmod(path, mode)
760 def copy2(self, src, dst):
761 return shutil.copy2(src, dst)
762 def exists(self, path):
763 return os.path.exists(path)
764 def getmtime(self, path):
765 return os.path.getmtime(path)
766 def getsize(self, path):
767 return os.path.getsize(path)
768 def isdir(self, path):
769 return os.path.isdir(path)
770 def isfile(self, path):
771 return os.path.isfile(path)
772 def link(self, src, dst):
773 return os.link(src, dst)
774 def lstat(self, path):
775 return os.lstat(path)
776 def listdir(self, path):
777 return os.listdir(path)
778 def makedirs(self, path):
779 return os.makedirs(path)
780 def mkdir(self, path):
781 return os.mkdir(path)
782 def rename(self, old, new):
783 return os.rename(old, new)
784 def stat(self, path):
786 def symlink(self, src, dst):
787 return os.symlink(src, dst)
788 def open(self, path):
790 def unlink(self, path):
791 return os.unlink(path)
793 if hasattr(os, 'symlink'):
794 def islink(self, path):
795 return os.path.islink(path)
797 def islink(self, path):
798 return 0 # no symlinks
800 if SCons.Memoize.use_old_memoization():
802 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
803 def __init__(self, *args, **kw):
804 apply(_FSBase.__init__, (self,)+args, kw)
805 SCons.Memoize.Memoizer.__init__(self)
809 # # Skeleton for the obvious methods we might need from the
810 # # abstraction layer for a remote filesystem.
811 # def upload(self, local_src, remote_dst):
813 # def download(self, remote_src, local_dst):
819 def __init__(self, path = None):
820 """Initialize the Node.FS subsystem.
822 The supplied path is the top of the source tree, where we
823 expect to find the top-level build file. If no path is
824 supplied, the current directory is the default.
826 The path argument must be a valid absolute path.
828 if __debug__: logInstanceCreation(self, 'Node.FS')
830 self.SConstruct_dir = None
831 self.CachePath = None
832 self.cache_force = None
833 self.cache_show = None
834 self.max_drift = default_max_drift
837 self.pathTop = os.getcwd()
841 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
846 def clear_cache(self):
850 def set_SConstruct_dir(self, dir):
851 self.SConstruct_dir = dir
853 def get_max_drift(self):
854 return self.max_drift
856 def set_max_drift(self, max_drift):
857 self.max_drift = max_drift
862 def __checkClass(self, node, klass):
863 if isinstance(node, klass) or klass == Entry:
865 if node.__class__ == Entry:
866 node.__class__ = klass
869 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
870 (node.__class__.__name__, node.path, klass.__name__)
872 def _doLookup(self, fsclass, name, directory = None, create = 1):
873 """This method differs from the File and Dir factory methods in
874 one important way: the meaning of the directory parameter.
875 In this method, if directory is None or not supplied, the supplied
876 name is expected to be an absolute path. If you try to look up a
877 relative path with directory=None, then an AssertionError will be
882 # This is a stupid hack to compensate for the fact that
883 # the POSIX and Win32 versions of os.path.normpath() behave
884 # differently in older versions of Python. In particular,
886 # os.path.normpath('./') == '.'
888 # os.path.normpath('./') == ''
889 # os.path.normpath('.\\') == ''
891 # This is a definite bug in the Python library, but we have
894 path_orig = string.split(name, os.sep)
895 path_norm = string.split(_my_normcase(name), os.sep)
897 first_orig = path_orig.pop(0) # strip first element
898 first_norm = path_norm.pop(0) # strip first element
900 drive, path_first = os.path.splitdrive(first_orig)
902 path_orig = [ path_first, ] + path_orig
903 path_norm = [ _my_normcase(path_first), ] + path_norm
907 directory = self.Root[drive]
910 raise SCons.Errors.UserError
911 directory = RootDir(_my_normcase(drive), self)
912 self.Root[drive] = directory
917 last_orig = path_orig.pop() # strip last element
918 last_norm = path_norm.pop() # strip last element
920 # Lookup the directory
921 for orig, norm in map(None, path_orig, path_norm):
923 entries = directory.entries
924 except AttributeError:
925 # We tried to look up the entry in either an Entry or
926 # a File. Give whatever it is a chance to do what's
927 # appropriate: morph into a Dir or raise an exception.
928 directory.must_be_a_Dir()
929 entries = directory.entries
931 directory = entries[norm]
934 raise SCons.Errors.UserError
936 d = Dir(orig, directory, self)
938 # Check the file system (or not, as configured) to make
939 # sure there isn't already a file there.
942 directory.entries[norm] = d
943 directory.add_wkid(d)
946 directory.must_be_a_Dir()
949 e = directory.entries[last_norm]
952 raise SCons.Errors.UserError
954 result = fsclass(last_orig, directory, self)
956 # Check the file system (or not, as configured) to make
957 # sure there isn't already a directory at the path on
958 # disk where we just created a File node, and vice versa.
959 result.diskcheck_match()
961 directory.entries[last_norm] = result
962 directory.add_wkid(result)
964 result = self.__checkClass(e, fsclass)
967 def _transformPath(self, name, directory):
968 """Take care of setting up the correct top-level directory,
969 usually in preparation for a call to doLookup().
971 If the path name is prepended with a '#', then it is unconditionally
972 interpreted as relative to the top-level directory of this FS.
974 If directory is None, and name is a relative path,
975 then the same applies.
977 if name and name[0] == '#':
980 if name and (name[0] == os.sep or name[0] == '/'):
981 # Correct such that '#/foo' is equivalent
984 name = os.path.join('.', os.path.normpath(name))
986 directory = self._cwd
987 return (os.path.normpath(name), directory)
989 def chdir(self, dir, change_os_dir=0):
990 """Change the current working directory for lookups.
991 If change_os_dir is true, we will also change the "real" cwd
999 os.chdir(dir.abspath)
1004 def Entry(self, name, directory = None, create = 1, klass=None):
1005 """Lookup or create a generic Entry node with the specified name.
1006 If the name is a relative path (begins with ./, ../, or a file
1007 name), then it is looked up relative to the supplied directory
1008 node, or to the top level directory of the FS (supplied at
1009 construction time) if no directory is supplied.
1015 if isinstance(name, Base):
1016 return self.__checkClass(name, klass)
1018 if directory and not isinstance(directory, Dir):
1019 directory = self.Dir(directory)
1020 name, directory = self._transformPath(name, directory)
1021 return self._doLookup(klass, name, directory, create)
1023 def File(self, name, directory = None, create = 1):
1024 """Lookup or create a File node with the specified name. If
1025 the name is a relative path (begins with ./, ../, or a file name),
1026 then it is looked up relative to the supplied directory node,
1027 or to the top level directory of the FS (supplied at construction
1028 time) if no directory is supplied.
1030 This method will raise TypeError if a directory is found at the
1034 return self.Entry(name, directory, create, File)
1036 def Dir(self, name, directory = None, create = 1):
1037 """Lookup or create a Dir node with the specified name. If
1038 the name is a relative path (begins with ./, ../, or a file name),
1039 then it is looked up relative to the supplied directory node,
1040 or to the top level directory of the FS (supplied at construction
1041 time) if no directory is supplied.
1043 This method will raise TypeError if a normal file is found at the
1047 return self.Entry(name, directory, create, Dir)
1049 def BuildDir(self, build_dir, src_dir, duplicate=1):
1050 """Link the supplied build directory to the source directory
1051 for purposes of building files."""
1053 if not isinstance(src_dir, SCons.Node.Node):
1054 src_dir = self.Dir(src_dir)
1055 if not isinstance(build_dir, SCons.Node.Node):
1056 build_dir = self.Dir(build_dir)
1057 if src_dir.is_under(build_dir):
1058 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1059 if build_dir.srcdir:
1060 if build_dir.srcdir == src_dir:
1061 return # We already did this.
1062 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1063 build_dir.link(src_dir, duplicate)
1065 def Repository(self, *dirs):
1066 """Specify Repository directories to search."""
1068 if not isinstance(d, SCons.Node.Node):
1070 self.Top.addRepository(d)
1072 def Rfindalldirs(self, pathlist, cwd):
1074 if SCons.Util.is_String(pathlist):
1075 pathlist = string.split(pathlist, os.pathsep)
1076 if not SCons.Util.is_List(pathlist):
1077 pathlist = [pathlist]
1079 for path in filter(None, pathlist):
1080 if isinstance(path, SCons.Node.Node):
1083 path, dir = self._transformPath(path, cwd)
1085 result.extend(dir.get_all_rdirs())
1088 def CacheDir(self, path):
1089 self.CachePath = path
1091 def build_dir_target_climb(self, orig, dir, tail):
1092 """Create targets in corresponding build directories
1094 Climb the directory tree, and look up path names
1095 relative to any linked build directories we find.
1100 fmt = "building associated BuildDir targets: %s"
1103 for bd in dir.build_dirs:
1104 if start_dir.is_under(bd):
1105 # If already in the build-dir location, don't reflect
1106 return [orig], fmt % str(orig)
1107 p = apply(os.path.join, [bd.path] + tail)
1108 targets.append(self.Entry(p))
1109 tail = [dir.name] + tail
1112 message = fmt % string.join(map(str, targets))
1113 return targets, message
1116 """A class for directories in a file system.
1119 def __init__(self, name, directory, fs):
1120 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1121 Base.__init__(self, name, directory, fs)
1125 """Turn a file system Node (either a freshly initialized directory
1126 object or a separate Entry object) into a proper directory object.
1128 Set up this directory's entries and hook it into the file
1129 system tree. Specify that directories (this Node) don't use
1130 signatures for calculating whether they're current.
1133 self.repositories = []
1137 self.entries['.'] = self
1138 self.entries['..'] = self.dir
1141 self._sconsign = None
1142 self.build_dirs = []
1144 # Don't just reset the executor, replace its action list,
1145 # because it might have some pre-or post-actions that need to
1147 self.builder = get_MkdirBuilder()
1148 self.get_executor().set_action_list(self.builder.action)
1150 def diskcheck_match(self):
1151 diskcheck_match(self, self.fs.isfile,
1152 "File %s found where directory expected.")
1154 def disambiguate(self):
1157 def __clearRepositoryCache(self, duplicate=None):
1158 """Called when we change the repository(ies) for a directory.
1159 This clears any cached information that is invalidated by changing
1162 for node in self.entries.values():
1163 if node != self.dir:
1164 if node != self and isinstance(node, Dir):
1165 node.__clearRepositoryCache(duplicate)
1170 except AttributeError:
1172 if duplicate != None:
1173 node.duplicate=duplicate
1175 def __resetDuplicate(self, node):
1177 node.duplicate = node.get_dir().duplicate
1179 def Entry(self, name):
1180 """Create an entry node named 'name' relative to this directory."""
1181 return self.fs.Entry(name, self)
1183 def Dir(self, name):
1184 """Create a directory node named 'name' relative to this directory."""
1185 return self.fs.Dir(name, self)
1187 def File(self, name):
1188 """Create a file node named 'name' relative to this directory."""
1189 return self.fs.File(name, self)
1191 def link(self, srcdir, duplicate):
1192 """Set this directory as the build directory for the
1193 supplied source directory."""
1194 self.srcdir = srcdir
1195 self.duplicate = duplicate
1196 self.__clearRepositoryCache(duplicate)
1197 srcdir.build_dirs.append(self)
1199 def getRepositories(self):
1200 """Returns a list of repositories for this directory.
1202 if self.srcdir and not self.duplicate:
1203 return self.srcdir.get_all_rdirs() + self.repositories
1204 return self.repositories
1206 def get_all_rdirs(self):
1212 for rep in dir.getRepositories():
1213 result.append(rep.Dir(fname))
1214 fname = dir.name + os.sep + fname
1218 def addRepository(self, dir):
1219 if dir != self and not dir in self.repositories:
1220 self.repositories.append(dir)
1222 self.__clearRepositoryCache()
1225 return self.entries['..']
1227 def rel_path(self, other):
1228 """Return a path to "other" relative to this directory.
1230 if isinstance(other, Dir):
1236 except AttributeError:
1239 return name and name[0] or '.'
1241 for x, y in map(None, self.path_elements, other.path_elements):
1245 path_elems = ['..']*(len(self.path_elements)-i) \
1246 + map(lambda n: n.name, other.path_elements[i:]) \
1249 return string.join(path_elems, os.sep)
1252 if not self.implicit is None:
1255 self.implicit_dict = {}
1256 self._children_reset()
1258 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1259 deps = filter(dont_scan, self.entries.keys())
1260 # keys() is going to give back the entries in an internal,
1261 # unsorted order. Sort 'em so the order is deterministic.
1263 entries = map(lambda n, e=self.entries: e[n], deps)
1265 self._add_child(self.implicit, self.implicit_dict, entries)
1267 def get_found_includes(self, env, scanner, path):
1268 """Return the included implicit dependencies in this file.
1269 Cache results so we only scan the file once per path
1270 regardless of how many times this information is requested.
1274 # Clear cached info for this Node. If we already visited this
1275 # directory on our walk down the tree (because we didn't know at
1276 # that point it was being used as the source for another Node)
1277 # then we may have calculated build signature before realizing
1278 # we had to scan the disk. Now that we have to, though, we need
1279 # to invalidate the old calculated signature so that any node
1280 # dependent on our directory structure gets one that includes
1281 # info about everything on disk.
1283 return scanner(self, env, path)
1285 def build(self, **kw):
1286 """A null "builder" for directories."""
1288 if not self.builder is MkdirBuilder:
1289 apply(SCons.Node.Node.build, [self,], kw)
1292 """Create this directory, silently and without worrying about
1293 whether the builder is the default or not."""
1299 listDirs.append(parent)
1302 raise SCons.Errors.StopError, parent.path
1305 for dirnode in listDirs:
1307 # Don't call dirnode.build(), call the base Node method
1308 # directly because we definitely *must* create this
1309 # directory. The dirnode.build() method will suppress
1310 # the build if it's the default builder.
1311 SCons.Node.Node.build(dirnode)
1312 dirnode.get_executor().nullify()
1313 # The build() action may or may not have actually
1314 # created the directory, depending on whether the -n
1315 # option was used or not. Delete the _exists and
1316 # _rexists attributes so they can be reevaluated.
1321 def multiple_side_effect_has_builder(self):
1323 return not self.builder is MkdirBuilder and self.has_builder()
1325 def alter_targets(self):
1326 """Return any corresponding targets in a build directory.
1328 return self.fs.build_dir_target_climb(self, self, [])
1330 def scanner_key(self):
1331 """A directory does not get scanned."""
1334 def get_contents(self):
1335 """Return aggregate contents of all our children."""
1336 contents = cStringIO.StringIO()
1337 for kid in self.children():
1338 contents.write(kid.get_contents())
1339 return contents.getvalue()
1344 def do_duplicate(self, src):
1347 def current(self, calc=None):
1348 """If all of our children were up-to-date, then this
1349 directory was up-to-date, too."""
1350 if not self.builder is MkdirBuilder and not self.exists():
1353 for kid in self.children():
1355 if s and (not state or s > state):
1358 if state == 0 or state == SCons.Node.up_to_date:
1365 if not self.exists():
1366 norm_name = _my_normcase(self.name)
1367 for dir in self.dir.get_all_rdirs():
1368 try: node = dir.entries[norm_name]
1369 except KeyError: node = dir.dir_on_disk(self.name)
1370 if node and node.exists() and \
1371 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1376 """Return the .sconsign file info for this directory,
1377 creating it first if necessary."""
1378 if not self._sconsign:
1379 import SCons.SConsign
1380 self._sconsign = SCons.SConsign.ForDirectory(self)
1381 return self._sconsign
1384 """Dir has a special need for srcnode()...if we
1385 have a srcdir attribute set, then that *is* our srcnode."""
1388 return Base.srcnode(self)
1390 def get_timestamp(self):
1391 """Return the latest timestamp from among our children"""
1393 for kid in self.children():
1394 if kid.get_timestamp() > stamp:
1395 stamp = kid.get_timestamp()
1398 def entry_abspath(self, name):
1399 return self.abspath + os.sep + name
1401 def entry_path(self, name):
1402 return self.path + os.sep + name
1404 def entry_tpath(self, name):
1405 return self.tpath + os.sep + name
1407 def must_be_a_Dir(self):
1408 """Called to make sure a Node is a Dir. Since we're already
1409 one, this is a no-op for us."""
1412 def entry_exists_on_disk(self, name):
1414 return self.fs.exists(self.entry_abspath(name))
1416 def srcdir_list(self):
1424 d = dir.srcdir.Dir(dirname)
1426 # Shouldn't source from something in the build path:
1427 # build_dir is probably under src_dir, in which case
1428 # we are reflecting.
1431 dirname = dir.name + os.sep + dirname
1436 def srcdir_duplicate(self, name):
1437 for dir in self.srcdir_list():
1438 if dir.entry_exists_on_disk(name):
1439 srcnode = dir.File(name)
1441 node = self.File(name)
1442 node.do_duplicate(srcnode)
1448 def srcdir_find_file(self, filename):
1451 if (isinstance(node, File) or isinstance(node, Entry)) and \
1452 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1456 norm_name = _my_normcase(filename)
1458 for rdir in self.get_all_rdirs():
1459 try: node = rdir.entries[norm_name]
1460 except KeyError: node = rdir.file_on_disk(filename)
1461 else: node = func(node)
1465 for srcdir in self.srcdir_list():
1466 for rdir in srcdir.get_all_rdirs():
1467 try: node = rdir.entries[norm_name]
1468 except KeyError: node = rdir.file_on_disk(filename)
1469 else: node = func(node)
1471 return File(filename, self, self.fs), srcdir
1475 def dir_on_disk(self, name):
1476 if self.entry_exists_on_disk(name):
1477 try: return self.Dir(name)
1478 except TypeError: pass
1481 def file_on_disk(self, name):
1482 if self.entry_exists_on_disk(name) or \
1483 diskcheck_rcs(self, name) or \
1484 diskcheck_sccs(self, name):
1485 try: return self.File(name)
1486 except TypeError: pass
1487 return self.srcdir_duplicate(name)
1490 """A class for the root directory of a file system.
1492 This is the same as a Dir class, except that the path separator
1493 ('/' or '\\') is actually part of the name, so we don't need to
1494 add a separator when creating the path names of entries within
1497 def __init__(self, name, fs):
1498 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1499 # We're going to be our own parent directory (".." entry and .dir
1500 # attribute) so we have to set up some values so Base.__init__()
1501 # won't gag won't it calls some of our methods.
1505 self.path_elements = []
1507 Base.__init__(self, name, self, fs)
1509 # Now set our paths to what we really want them to be: the
1510 # initial drive letter (the name) plus the directory separator.
1511 self.abspath = name + os.sep
1512 self.path = name + os.sep
1513 self.tpath = name + os.sep
1519 def entry_abspath(self, name):
1520 return self.abspath + name
1522 def entry_path(self, name):
1523 return self.path + name
1525 def entry_tpath(self, name):
1526 return self.tpath + name
1528 def is_under(self, dir):
1540 def src_builder(self):
1543 class NodeInfo(SCons.Node.NodeInfo):
1544 # The bsig attributes needs to stay here, if it's initialized in
1545 # __init__() then the assignment seems to overwrite any values
1546 # unpickled from .sconsign files.
1548 def __cmp__(self, other):
1549 return cmp(self.bsig, other.bsig)
1550 def update(self, node):
1551 self.timestamp = node.get_timestamp()
1552 self.size = node.getsize()
1554 class BuildInfo(SCons.Node.BuildInfo):
1555 def __init__(self, node):
1556 SCons.Node.BuildInfo.__init__(self, node)
1558 def convert_to_sconsign(self):
1559 """Convert this BuildInfo object for writing to a .sconsign file
1561 We hung onto the node that we refer to so that we can translate
1562 the lists of bsources, bdepends and bimplicit Nodes into strings
1563 relative to the node, but we don't want to write out that Node
1564 itself to the .sconsign file, so we delete the attribute in
1567 rel_path = self.node.rel_path
1568 delattr(self, 'node')
1569 for attr in ['bsources', 'bdepends', 'bimplicit']:
1571 val = getattr(self, attr)
1572 except AttributeError:
1575 setattr(self, attr, map(rel_path, val))
1576 def convert_from_sconsign(self, dir, name):
1577 """Convert a newly-read BuildInfo object for in-SCons use
1579 An on-disk BuildInfo comes without a reference to the node
1580 for which it's intended, so we have to convert the arguments
1581 and add back a self.node attribute. The bsources, bdepends and
1582 bimplicit lists all come from disk as paths relative to that node,
1583 so convert them to actual Nodes for use by the rest of SCons.
1585 self.node = dir.Entry(name)
1586 Entry_func = self.node.dir.Entry
1587 for attr in ['bsources', 'bdepends', 'bimplicit']:
1589 val = getattr(self, attr)
1590 except AttributeError:
1593 setattr(self, attr, map(Entry_func, val))
1596 """A class for files in a file system.
1598 def diskcheck_match(self):
1599 diskcheck_match(self, self.fs.isdir,
1600 "Directory %s found where file expected.")
1602 def __init__(self, name, directory, fs):
1603 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1604 Base.__init__(self, name, directory, fs)
1607 def Entry(self, name):
1608 """Create an entry node named 'name' relative to
1609 the SConscript directory of this file."""
1610 return self.fs.Entry(name, self.cwd)
1612 def Dir(self, name):
1613 """Create a directory node named 'name' relative to
1614 the SConscript directory of this file."""
1615 return self.fs.Dir(name, self.cwd)
1617 def Dirs(self, pathlist):
1618 """Create a list of directories relative to the SConscript
1619 directory of this file."""
1620 return map(lambda p, s=self: s.Dir(p), pathlist)
1622 def File(self, name):
1623 """Create a file node named 'name' relative to
1624 the SConscript directory of this file."""
1625 return self.fs.File(name, self.cwd)
1627 def RDirs(self, pathlist):
1628 """Search for a list of directories in the Repository list."""
1629 return self.fs.Rfindalldirs(pathlist, self.cwd)
1632 """Turn a file system node into a File object. __cache_reset__"""
1633 self.scanner_paths = {}
1634 if not hasattr(self, '_local'):
1637 def disambiguate(self):
1640 def scanner_key(self):
1641 return self.get_suffix()
1643 def get_contents(self):
1644 if not self.rexists():
1646 return open(self.rfile().abspath, "rb").read()
1648 def get_timestamp(self):
1650 return self.rfile().getmtime()
1654 def store_info(self, obj):
1655 # Merge our build information into the already-stored entry.
1656 # This accomodates "chained builds" where a file that's a target
1657 # in one build (SConstruct file) is a source in a different build.
1658 # See test/chained-build.py for the use case.
1659 entry = self.get_stored_info()
1661 self.dir.sconsign().set_entry(self.name, entry)
1663 def get_stored_info(self):
1666 stored = self.dir.sconsign().get_entry(self.name)
1667 except (KeyError, OSError):
1668 return self.new_binfo()
1670 if not hasattr(stored, 'ninfo'):
1671 # Transition: The .sconsign file entry has no NodeInfo
1672 # object, which means it's a slightly older BuildInfo.
1673 # Copy over the relevant attributes.
1674 ninfo = stored.ninfo = self.new_ninfo()
1675 for attr in ninfo.__dict__.keys():
1677 setattr(ninfo, attr, getattr(stored, attr))
1678 except AttributeError:
1682 def get_stored_implicit(self):
1683 binfo = self.get_stored_info()
1684 try: return binfo.bimplicit
1685 except AttributeError: return None
1687 def rel_path(self, other):
1688 return self.dir.rel_path(other)
1690 def get_found_includes(self, env, scanner, path):
1691 """Return the included implicit dependencies in this file.
1692 Cache results so we only scan the file once per path
1693 regardless of how many times this information is requested.
1697 return scanner(self, env, path)
1699 def _createDir(self):
1700 # ensure that the directories for this node are
1704 def retrieve_from_cache(self):
1705 """Try to retrieve the node's content from a cache
1707 This method is called from multiple threads in a parallel build,
1708 so only do thread safe stuff here. Do thread unsafe stuff in
1711 Note that there's a special trick here with the execute flag
1712 (one that's not normally done for other actions). Basically
1713 if the user requested a noexec (-n) build, then
1714 SCons.Action.execute_actions is set to 0 and when any action
1715 is called, it does its showing but then just returns zero
1716 instead of actually calling the action execution operation.
1717 The problem for caching is that if the file does NOT exist in
1718 cache then the CacheRetrieveString won't return anything to
1719 show for the task, but the Action.__call__ won't call
1720 CacheRetrieveFunc; instead it just returns zero, which makes
1721 the code below think that the file *was* successfully
1722 retrieved from the cache, therefore it doesn't do any
1723 subsequent building. However, the CacheRetrieveString didn't
1724 print anything because it didn't actually exist in the cache,
1725 and no more build actions will be performed, so the user just
1726 sees nothing. The fix is to tell Action.__call__ to always
1727 execute the CacheRetrieveFunc and then have the latter
1728 explicitly check SCons.Action.execute_actions itself.
1730 Returns true iff the node was successfully retrieved.
1732 b = self.is_derived()
1733 if not b and not self.has_src_builder():
1735 if b and self.fs.CachePath:
1736 if self.fs.cache_show:
1737 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1738 self.build(presub=0, execute=0)
1740 elif CacheRetrieve(self, [], None, execute=1) == 0:
1745 """Called just after this node is successfully built.
1747 # Push this file out to cache before the superclass Node.built()
1748 # method has a chance to clear the build signature, which it
1749 # will do if this file has a source scanner.
1750 if self.fs.CachePath and self.exists():
1751 CachePush(self, [], None)
1752 self.fs.clear_cache()
1753 SCons.Node.Node.built(self)
1756 if self.fs.CachePath and self.fs.cache_force and self.exists():
1757 CachePush(self, None, None)
1759 def has_src_builder(self):
1760 """Return whether this Node has a source builder or not.
1762 If this Node doesn't have an explicit source code builder, this
1763 is where we figure out, on the fly, if there's a transparent
1764 source code builder for it.
1766 Note that if we found a source builder, we also set the
1767 self.builder attribute, so that all of the methods that actually
1768 *build* this file don't have to do anything different.
1772 except AttributeError:
1776 scb = self.dir.src_builder()
1778 if diskcheck_sccs(self.dir, self.name):
1779 scb = get_DefaultSCCSBuilder()
1780 elif diskcheck_rcs(self.dir, self.name):
1781 scb = get_DefaultRCSBuilder()
1785 self.builder_set(scb)
1787 return not scb is None
1789 def alter_targets(self):
1790 """Return any corresponding targets in a build directory.
1792 if self.is_derived():
1794 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1796 def is_pseudo_derived(self):
1798 return self.has_src_builder()
1800 def _rmv_existing(self):
1802 Unlink(self, [], None)
1805 """Prepare for this file to be created."""
1806 SCons.Node.Node.prepare(self)
1808 if self.get_state() != SCons.Node.up_to_date:
1810 if self.is_derived() and not self.precious:
1811 self._rmv_existing()
1815 except SCons.Errors.StopError, drive:
1816 desc = "No drive `%s' for target `%s'." % (drive, self)
1817 raise SCons.Errors.StopError, desc
1820 """Remove this file."""
1821 if self.exists() or self.islink():
1822 self.fs.unlink(self.path)
1826 def do_duplicate(self, src):
1829 Unlink(self, None, None)
1830 except SCons.Errors.BuildError:
1833 Link(self, src, None)
1834 except SCons.Errors.BuildError, e:
1835 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1836 raise SCons.Errors.StopError, desc
1838 # The Link() action may or may not have actually
1839 # created the file, depending on whether the -n
1840 # option was used or not. Delete the _exists and
1841 # _rexists attributes so they can be reevaluated.
1846 # Duplicate from source path if we are set up to do this.
1847 if self.duplicate and not self.is_derived() and not self.linked:
1850 return Base.exists(self)
1852 if src.abspath != self.abspath and src.exists():
1853 self.do_duplicate(src)
1854 return Base.exists(self)
1857 # SIGNATURE SUBSYSTEM
1860 def new_binfo(self):
1861 return BuildInfo(self)
1863 def new_ninfo(self):
1868 def get_csig(self, calc=None):
1870 Generate a node's content signature, the digested signature
1874 cache - alternate node to use for the signature cache
1875 returns - the content signature
1878 return self.binfo.ninfo.csig
1879 except AttributeError:
1883 calc = self.calculator()
1885 max_drift = self.fs.max_drift
1886 mtime = self.get_timestamp()
1887 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
1891 old = self.get_stored_info().ninfo
1893 if old.timestamp and old.csig and old.timestamp == mtime:
1895 except AttributeError:
1898 csig = calc.module.signature(self)
1900 binfo = self.get_binfo()
1906 self.store_info(binfo)
1914 def current(self, calc=None):
1915 self.binfo = self.gen_binfo(calc)
1919 if self.always_build:
1921 if not self.exists():
1922 # The file doesn't exist locally...
1925 # ...but there is one in a Repository...
1926 old = r.get_stored_info()
1927 new = self.get_binfo()
1929 # ...and it's even up-to-date...
1931 # ...and they'd like a local copy.
1932 LocalCopy(self, r, None)
1933 self.store_info(new)
1937 old = self.get_stored_info()
1938 new = self.get_binfo()
1943 if not self.exists():
1944 norm_name = _my_normcase(self.name)
1945 for dir in self.dir.get_all_rdirs():
1946 try: node = dir.entries[norm_name]
1947 except KeyError: node = dir.file_on_disk(self.name)
1948 if node and node.exists() and \
1949 (isinstance(node, File) or isinstance(node, Entry) \
1950 or not node.is_derived()):
1955 return str(self.rfile())
1957 def cachepath(self):
1958 if not self.fs.CachePath:
1960 ninfo = self.get_binfo().ninfo
1961 if not hasattr(ninfo, 'bsig'):
1962 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
1963 elif ninfo.bsig is None:
1964 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1965 # Add the path to the cache signature, because multiple
1966 # targets built by the same action will all have the same
1967 # build signature, and we have to differentiate them somehow.
1968 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
1969 subdir = string.upper(cache_sig[0])
1970 dir = os.path.join(self.fs.CachePath, subdir)
1971 return dir, os.path.join(dir, cache_sig)
1973 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1974 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1976 def must_be_a_Dir(self):
1977 """Called to make sure a Node is a Dir. Since we're already a
1978 File, this is a TypeError..."""
1979 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1983 def find_file(filename, paths, verbose=None):
1985 find_file(str, [Dir()]) -> [nodes]
1987 filename - a filename to find
1988 paths - a list of directory path *nodes* to search in. Can be
1989 represented as a list, a tuple, or a callable that is
1990 called with no arguments and returns the list or tuple.
1992 returns - the node created from the found file.
1994 Find a node corresponding to either a derived file or a file
1995 that exists already.
1997 Only the first file found is returned, and none is returned
1998 if no file is found.
2002 if not SCons.Util.is_String(verbose):
2003 verbose = "find_file"
2004 if not callable(verbose):
2005 verbose = ' %s: ' % verbose
2006 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2008 verbose = lambda x: x
2013 # Give Entries a chance to morph into Dirs.
2014 paths = map(lambda p: p.must_be_a_Dir(), paths)
2016 filedir, filename = os.path.split(filename)
2018 def filedir_lookup(p, fd=filedir):
2022 # We tried to look up a Dir, but it seems there's already
2023 # a File (or something else) there. No big.
2025 paths = filter(None, map(filedir_lookup, paths))
2028 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2029 node, d = dir.srcdir_find_file(filename)
2031 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2035 def find_files(filenames, paths):
2037 find_files([str], [Dir()]) -> [nodes]
2039 filenames - a list of filenames to find
2040 paths - a list of directory path *nodes* to search in
2042 returns - the nodes created from the found files.
2044 Finds nodes corresponding to either derived files or files
2047 Only the first file found is returned for each filename,
2048 and any files that aren't found are ignored.
2050 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2051 return filter(None, nodes)