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
905 drive = _my_normcase(drive)
908 directory = self.Root[drive]
911 raise SCons.Errors.UserError
912 directory = RootDir(drive, self)
913 self.Root[drive] = directory
918 last_orig = path_orig.pop() # strip last element
919 last_norm = path_norm.pop() # strip last element
921 # Lookup the directory
922 for orig, norm in map(None, path_orig, path_norm):
924 entries = directory.entries
925 except AttributeError:
926 # We tried to look up the entry in either an Entry or
927 # a File. Give whatever it is a chance to do what's
928 # appropriate: morph into a Dir or raise an exception.
929 directory.must_be_a_Dir()
930 entries = directory.entries
932 directory = entries[norm]
935 raise SCons.Errors.UserError
937 d = Dir(orig, directory, self)
939 # Check the file system (or not, as configured) to make
940 # sure there isn't already a file there.
943 directory.entries[norm] = d
944 directory.add_wkid(d)
947 directory.must_be_a_Dir()
950 e = directory.entries[last_norm]
953 raise SCons.Errors.UserError
955 result = fsclass(last_orig, directory, self)
957 # Check the file system (or not, as configured) to make
958 # sure there isn't already a directory at the path on
959 # disk where we just created a File node, and vice versa.
960 result.diskcheck_match()
962 directory.entries[last_norm] = result
963 directory.add_wkid(result)
965 result = self.__checkClass(e, fsclass)
968 def _transformPath(self, name, directory):
969 """Take care of setting up the correct top-level directory,
970 usually in preparation for a call to doLookup().
972 If the path name is prepended with a '#', then it is unconditionally
973 interpreted as relative to the top-level directory of this FS.
975 If directory is None, and name is a relative path,
976 then the same applies.
978 if name and name[0] == '#':
981 if name and (name[0] == os.sep or name[0] == '/'):
982 # Correct such that '#/foo' is equivalent
985 name = os.path.join('.', os.path.normpath(name))
987 directory = self._cwd
988 return (os.path.normpath(name), directory)
990 def chdir(self, dir, change_os_dir=0):
991 """Change the current working directory for lookups.
992 If change_os_dir is true, we will also change the "real" cwd
1000 os.chdir(dir.abspath)
1005 def Entry(self, name, directory = None, create = 1, klass=None):
1006 """Lookup or create a generic Entry node with the specified name.
1007 If the name is a relative path (begins with ./, ../, or a file
1008 name), then it is looked up relative to the supplied directory
1009 node, or to the top level directory of the FS (supplied at
1010 construction time) if no directory is supplied.
1016 if isinstance(name, Base):
1017 return self.__checkClass(name, klass)
1019 if directory and not isinstance(directory, Dir):
1020 directory = self.Dir(directory)
1021 name, directory = self._transformPath(name, directory)
1022 return self._doLookup(klass, name, directory, create)
1024 def File(self, name, directory = None, create = 1):
1025 """Lookup or create a File node with the specified name. If
1026 the name is a relative path (begins with ./, ../, or a file name),
1027 then it is looked up relative to the supplied directory node,
1028 or to the top level directory of the FS (supplied at construction
1029 time) if no directory is supplied.
1031 This method will raise TypeError if a directory is found at the
1035 return self.Entry(name, directory, create, File)
1037 def Dir(self, name, directory = None, create = 1):
1038 """Lookup or create a Dir node with the specified name. If
1039 the name is a relative path (begins with ./, ../, or a file name),
1040 then it is looked up relative to the supplied directory node,
1041 or to the top level directory of the FS (supplied at construction
1042 time) if no directory is supplied.
1044 This method will raise TypeError if a normal file is found at the
1048 return self.Entry(name, directory, create, Dir)
1050 def BuildDir(self, build_dir, src_dir, duplicate=1):
1051 """Link the supplied build directory to the source directory
1052 for purposes of building files."""
1054 if not isinstance(src_dir, SCons.Node.Node):
1055 src_dir = self.Dir(src_dir)
1056 if not isinstance(build_dir, SCons.Node.Node):
1057 build_dir = self.Dir(build_dir)
1058 if src_dir.is_under(build_dir):
1059 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1060 if build_dir.srcdir:
1061 if build_dir.srcdir == src_dir:
1062 return # We already did this.
1063 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1064 build_dir.link(src_dir, duplicate)
1066 def Repository(self, *dirs):
1067 """Specify Repository directories to search."""
1069 if not isinstance(d, SCons.Node.Node):
1071 self.Top.addRepository(d)
1073 def Rfindalldirs(self, pathlist, cwd):
1075 if SCons.Util.is_String(pathlist):
1076 pathlist = string.split(pathlist, os.pathsep)
1077 if not SCons.Util.is_List(pathlist):
1078 pathlist = [pathlist]
1080 for path in filter(None, pathlist):
1081 if isinstance(path, SCons.Node.Node):
1084 path, dir = self._transformPath(path, cwd)
1086 result.extend(dir.get_all_rdirs())
1089 def CacheDir(self, path):
1090 self.CachePath = path
1092 def build_dir_target_climb(self, orig, dir, tail):
1093 """Create targets in corresponding build directories
1095 Climb the directory tree, and look up path names
1096 relative to any linked build directories we find.
1101 fmt = "building associated BuildDir targets: %s"
1104 for bd in dir.build_dirs:
1105 if start_dir.is_under(bd):
1106 # If already in the build-dir location, don't reflect
1107 return [orig], fmt % str(orig)
1108 p = apply(os.path.join, [bd.path] + tail)
1109 targets.append(self.Entry(p))
1110 tail = [dir.name] + tail
1113 message = fmt % string.join(map(str, targets))
1114 return targets, message
1117 """A class for directories in a file system.
1120 def __init__(self, name, directory, fs):
1121 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1122 Base.__init__(self, name, directory, fs)
1126 """Turn a file system Node (either a freshly initialized directory
1127 object or a separate Entry object) into a proper directory object.
1129 Set up this directory's entries and hook it into the file
1130 system tree. Specify that directories (this Node) don't use
1131 signatures for calculating whether they're current.
1134 self.repositories = []
1138 self.entries['.'] = self
1139 self.entries['..'] = self.dir
1142 self._sconsign = None
1143 self.build_dirs = []
1145 # Don't just reset the executor, replace its action list,
1146 # because it might have some pre-or post-actions that need to
1148 self.builder = get_MkdirBuilder()
1149 self.get_executor().set_action_list(self.builder.action)
1151 def diskcheck_match(self):
1152 diskcheck_match(self, self.fs.isfile,
1153 "File %s found where directory expected.")
1155 def disambiguate(self):
1158 def __clearRepositoryCache(self, duplicate=None):
1159 """Called when we change the repository(ies) for a directory.
1160 This clears any cached information that is invalidated by changing
1163 for node in self.entries.values():
1164 if node != self.dir:
1165 if node != self and isinstance(node, Dir):
1166 node.__clearRepositoryCache(duplicate)
1171 except AttributeError:
1173 if duplicate != None:
1174 node.duplicate=duplicate
1176 def __resetDuplicate(self, node):
1178 node.duplicate = node.get_dir().duplicate
1180 def Entry(self, name):
1181 """Create an entry node named 'name' relative to this directory."""
1182 return self.fs.Entry(name, self)
1184 def Dir(self, name):
1185 """Create a directory node named 'name' relative to this directory."""
1186 return self.fs.Dir(name, self)
1188 def File(self, name):
1189 """Create a file node named 'name' relative to this directory."""
1190 return self.fs.File(name, self)
1192 def link(self, srcdir, duplicate):
1193 """Set this directory as the build directory for the
1194 supplied source directory."""
1195 self.srcdir = srcdir
1196 self.duplicate = duplicate
1197 self.__clearRepositoryCache(duplicate)
1198 srcdir.build_dirs.append(self)
1200 def getRepositories(self):
1201 """Returns a list of repositories for this directory.
1203 if self.srcdir and not self.duplicate:
1204 return self.srcdir.get_all_rdirs() + self.repositories
1205 return self.repositories
1207 def get_all_rdirs(self):
1213 for rep in dir.getRepositories():
1214 result.append(rep.Dir(fname))
1215 fname = dir.name + os.sep + fname
1219 def addRepository(self, dir):
1220 if dir != self and not dir in self.repositories:
1221 self.repositories.append(dir)
1223 self.__clearRepositoryCache()
1226 return self.entries['..']
1228 def rel_path(self, other):
1229 """Return a path to "other" relative to this directory.
1231 if isinstance(other, Dir):
1237 except AttributeError:
1240 return name and name[0] or '.'
1242 for x, y in map(None, self.path_elements, other.path_elements):
1246 path_elems = ['..']*(len(self.path_elements)-i) \
1247 + map(lambda n: n.name, other.path_elements[i:]) \
1250 return string.join(path_elems, os.sep)
1253 if not self.implicit is None:
1256 self.implicit_dict = {}
1257 self._children_reset()
1259 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1260 deps = filter(dont_scan, self.entries.keys())
1261 # keys() is going to give back the entries in an internal,
1262 # unsorted order. Sort 'em so the order is deterministic.
1264 entries = map(lambda n, e=self.entries: e[n], deps)
1266 self._add_child(self.implicit, self.implicit_dict, entries)
1268 def get_found_includes(self, env, scanner, path):
1269 """Return the included implicit dependencies in this file.
1270 Cache results so we only scan the file once per path
1271 regardless of how many times this information is requested.
1275 # Clear cached info for this Node. If we already visited this
1276 # directory on our walk down the tree (because we didn't know at
1277 # that point it was being used as the source for another Node)
1278 # then we may have calculated build signature before realizing
1279 # we had to scan the disk. Now that we have to, though, we need
1280 # to invalidate the old calculated signature so that any node
1281 # dependent on our directory structure gets one that includes
1282 # info about everything on disk.
1284 return scanner(self, env, path)
1286 def build(self, **kw):
1287 """A null "builder" for directories."""
1289 if not self.builder is MkdirBuilder:
1290 apply(SCons.Node.Node.build, [self,], kw)
1293 """Create this directory, silently and without worrying about
1294 whether the builder is the default or not."""
1300 listDirs.append(parent)
1303 raise SCons.Errors.StopError, parent.path
1306 for dirnode in listDirs:
1308 # Don't call dirnode.build(), call the base Node method
1309 # directly because we definitely *must* create this
1310 # directory. The dirnode.build() method will suppress
1311 # the build if it's the default builder.
1312 SCons.Node.Node.build(dirnode)
1313 dirnode.get_executor().nullify()
1314 # The build() action may or may not have actually
1315 # created the directory, depending on whether the -n
1316 # option was used or not. Delete the _exists and
1317 # _rexists attributes so they can be reevaluated.
1322 def multiple_side_effect_has_builder(self):
1324 return not self.builder is MkdirBuilder and self.has_builder()
1326 def alter_targets(self):
1327 """Return any corresponding targets in a build directory.
1329 return self.fs.build_dir_target_climb(self, self, [])
1331 def scanner_key(self):
1332 """A directory does not get scanned."""
1335 def get_contents(self):
1336 """Return aggregate contents of all our children."""
1337 contents = cStringIO.StringIO()
1338 for kid in self.children():
1339 contents.write(kid.get_contents())
1340 return contents.getvalue()
1345 def do_duplicate(self, src):
1348 def current(self, calc=None):
1349 """If all of our children were up-to-date, then this
1350 directory was up-to-date, too."""
1351 if not self.builder is MkdirBuilder and not self.exists():
1354 for kid in self.children():
1356 if s and (not state or s > state):
1359 if state == 0 or state == SCons.Node.up_to_date:
1366 if not self.exists():
1367 norm_name = _my_normcase(self.name)
1368 for dir in self.dir.get_all_rdirs():
1369 try: node = dir.entries[norm_name]
1370 except KeyError: node = dir.dir_on_disk(self.name)
1371 if node and node.exists() and \
1372 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1377 """Return the .sconsign file info for this directory,
1378 creating it first if necessary."""
1379 if not self._sconsign:
1380 import SCons.SConsign
1381 self._sconsign = SCons.SConsign.ForDirectory(self)
1382 return self._sconsign
1385 """Dir has a special need for srcnode()...if we
1386 have a srcdir attribute set, then that *is* our srcnode."""
1389 return Base.srcnode(self)
1391 def get_timestamp(self):
1392 """Return the latest timestamp from among our children"""
1394 for kid in self.children():
1395 if kid.get_timestamp() > stamp:
1396 stamp = kid.get_timestamp()
1399 def entry_abspath(self, name):
1400 return self.abspath + os.sep + name
1402 def entry_path(self, name):
1403 return self.path + os.sep + name
1405 def entry_tpath(self, name):
1406 return self.tpath + os.sep + name
1408 def must_be_a_Dir(self):
1409 """Called to make sure a Node is a Dir. Since we're already
1410 one, this is a no-op for us."""
1413 def entry_exists_on_disk(self, name):
1415 return self.fs.exists(self.entry_abspath(name))
1417 def srcdir_list(self):
1425 d = dir.srcdir.Dir(dirname)
1427 # Shouldn't source from something in the build path:
1428 # build_dir is probably under src_dir, in which case
1429 # we are reflecting.
1432 dirname = dir.name + os.sep + dirname
1437 def srcdir_duplicate(self, name):
1438 for dir in self.srcdir_list():
1439 if dir.entry_exists_on_disk(name):
1440 srcnode = dir.File(name)
1442 node = self.File(name)
1443 node.do_duplicate(srcnode)
1449 def srcdir_find_file(self, filename):
1452 if (isinstance(node, File) or isinstance(node, Entry)) and \
1453 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1457 norm_name = _my_normcase(filename)
1459 for rdir in self.get_all_rdirs():
1460 try: node = rdir.entries[norm_name]
1461 except KeyError: node = rdir.file_on_disk(filename)
1462 else: node = func(node)
1466 for srcdir in self.srcdir_list():
1467 for rdir in srcdir.get_all_rdirs():
1468 try: node = rdir.entries[norm_name]
1469 except KeyError: node = rdir.file_on_disk(filename)
1470 else: node = func(node)
1472 return File(filename, self, self.fs), srcdir
1476 def dir_on_disk(self, name):
1477 if self.entry_exists_on_disk(name):
1478 try: return self.Dir(name)
1479 except TypeError: pass
1482 def file_on_disk(self, name):
1483 if self.entry_exists_on_disk(name) or \
1484 diskcheck_rcs(self, name) or \
1485 diskcheck_sccs(self, name):
1486 try: return self.File(name)
1487 except TypeError: pass
1488 return self.srcdir_duplicate(name)
1491 """A class for the root directory of a file system.
1493 This is the same as a Dir class, except that the path separator
1494 ('/' or '\\') is actually part of the name, so we don't need to
1495 add a separator when creating the path names of entries within
1498 def __init__(self, name, fs):
1499 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1500 # We're going to be our own parent directory (".." entry and .dir
1501 # attribute) so we have to set up some values so Base.__init__()
1502 # won't gag won't it calls some of our methods.
1506 self.path_elements = []
1508 Base.__init__(self, name, self, fs)
1510 # Now set our paths to what we really want them to be: the
1511 # initial drive letter (the name) plus the directory separator.
1512 self.abspath = name + os.sep
1513 self.path = name + os.sep
1514 self.tpath = name + os.sep
1520 def entry_abspath(self, name):
1521 return self.abspath + name
1523 def entry_path(self, name):
1524 return self.path + name
1526 def entry_tpath(self, name):
1527 return self.tpath + name
1529 def is_under(self, dir):
1541 def src_builder(self):
1544 class NodeInfo(SCons.Node.NodeInfo):
1545 # The bsig attributes needs to stay here, if it's initialized in
1546 # __init__() then the assignment seems to overwrite any values
1547 # unpickled from .sconsign files.
1549 def __cmp__(self, other):
1550 return cmp(self.bsig, other.bsig)
1551 def update(self, node):
1552 self.timestamp = node.get_timestamp()
1553 self.size = node.getsize()
1555 class BuildInfo(SCons.Node.BuildInfo):
1556 def __init__(self, node):
1557 SCons.Node.BuildInfo.__init__(self, node)
1559 def convert_to_sconsign(self):
1560 """Convert this BuildInfo object for writing to a .sconsign file
1562 We hung onto the node that we refer to so that we can translate
1563 the lists of bsources, bdepends and bimplicit Nodes into strings
1564 relative to the node, but we don't want to write out that Node
1565 itself to the .sconsign file, so we delete the attribute in
1568 rel_path = self.node.rel_path
1569 delattr(self, 'node')
1570 for attr in ['bsources', 'bdepends', 'bimplicit']:
1572 val = getattr(self, attr)
1573 except AttributeError:
1576 setattr(self, attr, map(rel_path, val))
1577 def convert_from_sconsign(self, dir, name):
1578 """Convert a newly-read BuildInfo object for in-SCons use
1580 An on-disk BuildInfo comes without a reference to the node
1581 for which it's intended, so we have to convert the arguments
1582 and add back a self.node attribute. The bsources, bdepends and
1583 bimplicit lists all come from disk as paths relative to that node,
1584 so convert them to actual Nodes for use by the rest of SCons.
1586 self.node = dir.Entry(name)
1587 Entry_func = self.node.dir.Entry
1588 for attr in ['bsources', 'bdepends', 'bimplicit']:
1590 val = getattr(self, attr)
1591 except AttributeError:
1594 setattr(self, attr, map(Entry_func, val))
1597 """A class for files in a file system.
1599 def diskcheck_match(self):
1600 diskcheck_match(self, self.fs.isdir,
1601 "Directory %s found where file expected.")
1603 def __init__(self, name, directory, fs):
1604 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1605 Base.__init__(self, name, directory, fs)
1608 def Entry(self, name):
1609 """Create an entry node named 'name' relative to
1610 the SConscript directory of this file."""
1611 return self.fs.Entry(name, self.cwd)
1613 def Dir(self, name):
1614 """Create a directory node named 'name' relative to
1615 the SConscript directory of this file."""
1616 return self.fs.Dir(name, self.cwd)
1618 def Dirs(self, pathlist):
1619 """Create a list of directories relative to the SConscript
1620 directory of this file."""
1621 return map(lambda p, s=self: s.Dir(p), pathlist)
1623 def File(self, name):
1624 """Create a file node named 'name' relative to
1625 the SConscript directory of this file."""
1626 return self.fs.File(name, self.cwd)
1628 def RDirs(self, pathlist):
1629 """Search for a list of directories in the Repository list."""
1630 return self.fs.Rfindalldirs(pathlist, self.cwd)
1633 """Turn a file system node into a File object. __cache_reset__"""
1634 self.scanner_paths = {}
1635 if not hasattr(self, '_local'):
1638 def disambiguate(self):
1641 def scanner_key(self):
1642 return self.get_suffix()
1644 def get_contents(self):
1645 if not self.rexists():
1647 return open(self.rfile().abspath, "rb").read()
1649 def get_timestamp(self):
1651 return self.rfile().getmtime()
1655 def store_info(self, obj):
1656 # Merge our build information into the already-stored entry.
1657 # This accomodates "chained builds" where a file that's a target
1658 # in one build (SConstruct file) is a source in a different build.
1659 # See test/chained-build.py for the use case.
1660 entry = self.get_stored_info()
1662 self.dir.sconsign().set_entry(self.name, entry)
1664 def get_stored_info(self):
1667 stored = self.dir.sconsign().get_entry(self.name)
1668 except (KeyError, OSError):
1669 return self.new_binfo()
1671 if not hasattr(stored, 'ninfo'):
1672 # Transition: The .sconsign file entry has no NodeInfo
1673 # object, which means it's a slightly older BuildInfo.
1674 # Copy over the relevant attributes.
1675 ninfo = stored.ninfo = self.new_ninfo()
1676 for attr in ninfo.__dict__.keys():
1678 setattr(ninfo, attr, getattr(stored, attr))
1679 except AttributeError:
1683 def get_stored_implicit(self):
1684 binfo = self.get_stored_info()
1685 try: return binfo.bimplicit
1686 except AttributeError: return None
1688 def rel_path(self, other):
1689 return self.dir.rel_path(other)
1691 def get_found_includes(self, env, scanner, path):
1692 """Return the included implicit dependencies in this file.
1693 Cache results so we only scan the file once per path
1694 regardless of how many times this information is requested.
1698 return scanner(self, env, path)
1700 def _createDir(self):
1701 # ensure that the directories for this node are
1705 def retrieve_from_cache(self):
1706 """Try to retrieve the node's content from a cache
1708 This method is called from multiple threads in a parallel build,
1709 so only do thread safe stuff here. Do thread unsafe stuff in
1712 Note that there's a special trick here with the execute flag
1713 (one that's not normally done for other actions). Basically
1714 if the user requested a noexec (-n) build, then
1715 SCons.Action.execute_actions is set to 0 and when any action
1716 is called, it does its showing but then just returns zero
1717 instead of actually calling the action execution operation.
1718 The problem for caching is that if the file does NOT exist in
1719 cache then the CacheRetrieveString won't return anything to
1720 show for the task, but the Action.__call__ won't call
1721 CacheRetrieveFunc; instead it just returns zero, which makes
1722 the code below think that the file *was* successfully
1723 retrieved from the cache, therefore it doesn't do any
1724 subsequent building. However, the CacheRetrieveString didn't
1725 print anything because it didn't actually exist in the cache,
1726 and no more build actions will be performed, so the user just
1727 sees nothing. The fix is to tell Action.__call__ to always
1728 execute the CacheRetrieveFunc and then have the latter
1729 explicitly check SCons.Action.execute_actions itself.
1731 Returns true iff the node was successfully retrieved.
1733 b = self.is_derived()
1734 if not b and not self.has_src_builder():
1736 if b and self.fs.CachePath:
1737 if self.fs.cache_show:
1738 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1739 self.build(presub=0, execute=0)
1741 elif CacheRetrieve(self, [], None, execute=1) == 0:
1746 """Called just after this node is successfully built.
1748 # Push this file out to cache before the superclass Node.built()
1749 # method has a chance to clear the build signature, which it
1750 # will do if this file has a source scanner.
1751 if self.fs.CachePath and self.exists():
1752 CachePush(self, [], None)
1753 self.fs.clear_cache()
1754 SCons.Node.Node.built(self)
1757 if self.fs.CachePath and self.fs.cache_force and self.exists():
1758 CachePush(self, None, None)
1760 def has_src_builder(self):
1761 """Return whether this Node has a source builder or not.
1763 If this Node doesn't have an explicit source code builder, this
1764 is where we figure out, on the fly, if there's a transparent
1765 source code builder for it.
1767 Note that if we found a source builder, we also set the
1768 self.builder attribute, so that all of the methods that actually
1769 *build* this file don't have to do anything different.
1773 except AttributeError:
1777 scb = self.dir.src_builder()
1779 if diskcheck_sccs(self.dir, self.name):
1780 scb = get_DefaultSCCSBuilder()
1781 elif diskcheck_rcs(self.dir, self.name):
1782 scb = get_DefaultRCSBuilder()
1786 self.builder_set(scb)
1788 return not scb is None
1790 def alter_targets(self):
1791 """Return any corresponding targets in a build directory.
1793 if self.is_derived():
1795 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1797 def is_pseudo_derived(self):
1799 return self.has_src_builder()
1801 def _rmv_existing(self):
1803 Unlink(self, [], None)
1806 """Prepare for this file to be created."""
1807 SCons.Node.Node.prepare(self)
1809 if self.get_state() != SCons.Node.up_to_date:
1811 if self.is_derived() and not self.precious:
1812 self._rmv_existing()
1816 except SCons.Errors.StopError, drive:
1817 desc = "No drive `%s' for target `%s'." % (drive, self)
1818 raise SCons.Errors.StopError, desc
1821 """Remove this file."""
1822 if self.exists() or self.islink():
1823 self.fs.unlink(self.path)
1827 def do_duplicate(self, src):
1830 Unlink(self, None, None)
1831 except SCons.Errors.BuildError:
1834 Link(self, src, None)
1835 except SCons.Errors.BuildError, e:
1836 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1837 raise SCons.Errors.StopError, desc
1839 # The Link() action may or may not have actually
1840 # created the file, depending on whether the -n
1841 # option was used or not. Delete the _exists and
1842 # _rexists attributes so they can be reevaluated.
1847 # Duplicate from source path if we are set up to do this.
1848 if self.duplicate and not self.is_derived() and not self.linked:
1851 return Base.exists(self)
1853 if src.abspath != self.abspath and src.exists():
1854 self.do_duplicate(src)
1855 return Base.exists(self)
1858 # SIGNATURE SUBSYSTEM
1861 def new_binfo(self):
1862 return BuildInfo(self)
1864 def new_ninfo(self):
1869 def get_csig(self, calc=None):
1871 Generate a node's content signature, the digested signature
1875 cache - alternate node to use for the signature cache
1876 returns - the content signature
1879 return self.binfo.ninfo.csig
1880 except AttributeError:
1884 calc = self.calculator()
1886 max_drift = self.fs.max_drift
1887 mtime = self.get_timestamp()
1888 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
1892 old = self.get_stored_info().ninfo
1894 if old.timestamp and old.csig and old.timestamp == mtime:
1896 except AttributeError:
1899 csig = calc.module.signature(self)
1901 binfo = self.get_binfo()
1907 self.store_info(binfo)
1915 def current(self, calc=None):
1916 self.binfo = self.gen_binfo(calc)
1920 if self.always_build:
1922 if not self.exists():
1923 # The file doesn't exist locally...
1926 # ...but there is one in a Repository...
1927 old = r.get_stored_info()
1928 new = self.get_binfo()
1930 # ...and it's even up-to-date...
1932 # ...and they'd like a local copy.
1933 LocalCopy(self, r, None)
1934 self.store_info(new)
1938 old = self.get_stored_info()
1939 new = self.get_binfo()
1944 if not self.exists():
1945 norm_name = _my_normcase(self.name)
1946 for dir in self.dir.get_all_rdirs():
1947 try: node = dir.entries[norm_name]
1948 except KeyError: node = dir.file_on_disk(self.name)
1949 if node and node.exists() and \
1950 (isinstance(node, File) or isinstance(node, Entry) \
1951 or not node.is_derived()):
1956 return str(self.rfile())
1958 def cachepath(self):
1959 if not self.fs.CachePath:
1961 ninfo = self.get_binfo().ninfo
1962 if not hasattr(ninfo, 'bsig'):
1963 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
1964 elif ninfo.bsig is None:
1965 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1966 # Add the path to the cache signature, because multiple
1967 # targets built by the same action will all have the same
1968 # build signature, and we have to differentiate them somehow.
1969 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
1970 subdir = string.upper(cache_sig[0])
1971 dir = os.path.join(self.fs.CachePath, subdir)
1972 return dir, os.path.join(dir, cache_sig)
1974 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1975 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1977 def must_be_a_Dir(self):
1978 """Called to make sure a Node is a Dir. Since we're already a
1979 File, this is a TypeError..."""
1980 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1984 def find_file(filename, paths, verbose=None):
1986 find_file(str, [Dir()]) -> [nodes]
1988 filename - a filename to find
1989 paths - a list of directory path *nodes* to search in. Can be
1990 represented as a list, a tuple, or a callable that is
1991 called with no arguments and returns the list or tuple.
1993 returns - the node created from the found file.
1995 Find a node corresponding to either a derived file or a file
1996 that exists already.
1998 Only the first file found is returned, and none is returned
1999 if no file is found.
2003 if not SCons.Util.is_String(verbose):
2004 verbose = "find_file"
2005 if not callable(verbose):
2006 verbose = ' %s: ' % verbose
2007 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2009 verbose = lambda x: x
2014 # Give Entries a chance to morph into Dirs.
2015 paths = map(lambda p: p.must_be_a_Dir(), paths)
2017 filedir, filename = os.path.split(filename)
2019 def filedir_lookup(p, fd=filedir):
2023 # We tried to look up a Dir, but it seems there's already
2024 # a File (or something else) there. No big.
2026 paths = filter(None, map(filedir_lookup, paths))
2029 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2030 node, d = dir.srcdir_find_file(filename)
2032 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2036 def find_files(filenames, paths):
2038 find_files([str], [Dir()]) -> [nodes]
2040 filenames - a list of filenames to find
2041 paths - a list of directory path *nodes* to search in
2043 returns - the nodes created from the found files.
2045 Finds nodes corresponding to either derived files or files
2048 Only the first file found is returned for each filename,
2049 and any files that aren't found are ignored.
2051 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2052 return filter(None, nodes)