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:
159 except (IOError, OSError):
160 # An OSError indicates something happened like a permissions
161 # problem or an attempt to symlink across file-system
162 # boundaries. An IOError indicates something like the file
163 # not existing. In either case, keeping trying additional
164 # functions in the list and only raise an error if the last
166 if func == Link_Funcs[-1]:
167 # exception of the last link method (copy) are fatal
173 Link = SCons.Action.Action(LinkFunc, None)
174 def LocalString(target, source, env):
175 return 'Local copy of %s from %s' % (target[0], source[0])
177 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
179 def UnlinkFunc(target, source, env):
181 t.fs.unlink(t.abspath)
184 Unlink = SCons.Action.Action(UnlinkFunc, None)
186 def MkdirFunc(target, source, env):
189 t.fs.mkdir(t.abspath)
192 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
196 def get_MkdirBuilder():
198 if MkdirBuilder is None:
200 # "env" will get filled in by Executor.get_build_env()
201 # calling SCons.Defaults.DefaultEnvironment() when necessary.
202 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
206 name = "MkdirBuilder")
209 def CacheRetrieveFunc(target, source, env):
212 cachedir, cachefile = t.cachepath()
213 if fs.exists(cachefile):
214 if SCons.Action.execute_actions:
215 fs.copy2(cachefile, t.path)
216 st = fs.stat(cachefile)
217 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
221 def CacheRetrieveString(target, source, env):
223 cachedir, cachefile = t.cachepath()
224 if t.fs.exists(cachefile):
225 return "Retrieved `%s' from cache" % t.path
228 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
230 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
232 def CachePushFunc(target, source, env):
235 cachedir, cachefile = t.cachepath()
236 if fs.exists(cachefile):
237 # Don't bother copying it if it's already there.
240 if not fs.isdir(cachedir):
241 fs.makedirs(cachedir)
243 tempfile = cachefile+'.tmp'
245 fs.copy2(t.path, tempfile)
246 fs.rename(tempfile, cachefile)
248 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
249 except (IOError, OSError):
250 # It's possible someone else tried writing the file at the
251 # same time we did, or else that there was some problem like
252 # the CacheDir being on a separate file system that's full.
253 # In any case, inability to push a file to cache doesn't affect
254 # the correctness of the build, so just print a warning.
255 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
256 "Unable to copy %s to cache. Cache file is %s"
257 % (str(target), cachefile))
260 CachePush = SCons.Action.Action(CachePushFunc, None)
267 DefaultSCCSBuilder = None
268 DefaultRCSBuilder = None
270 def get_DefaultSCCSBuilder():
271 global DefaultSCCSBuilder
272 if DefaultSCCSBuilder is None:
274 # "env" will get filled in by Executor.get_build_env()
275 # calling SCons.Defaults.DefaultEnvironment() when necessary.
276 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
277 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
279 name = "DefaultSCCSBuilder")
280 return DefaultSCCSBuilder
282 def get_DefaultRCSBuilder():
283 global DefaultRCSBuilder
284 if DefaultRCSBuilder is None:
286 # "env" will get filled in by Executor.get_build_env()
287 # calling SCons.Defaults.DefaultEnvironment() when necessary.
288 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
289 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
291 name = "DefaultRCSBuilder")
292 return DefaultRCSBuilder
294 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
295 _is_cygwin = sys.platform == "cygwin"
296 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
301 return string.upper(x)
306 def __init__(self, type, do, ignore):
312 self.__call__ = self.do
313 def set_ignore(self):
314 self.__call__ = self.ignore
316 if self.type in list:
321 def do_diskcheck_match(node, predicate, errorfmt):
324 raise TypeError, errorfmt % path
326 def ignore_diskcheck_match(node, predicate, errorfmt):
329 def do_diskcheck_rcs(node, name):
330 rcspath = 'RCS' + os.sep + name+',v'
331 return node.entry_exists_on_disk(rcspath)
333 def ignore_diskcheck_rcs(node, name):
336 def do_diskcheck_sccs(node, name):
337 sccspath = 'SCCS' + os.sep + 's.'+name
338 return node.entry_exists_on_disk(sccspath)
340 def ignore_diskcheck_sccs(node, name):
343 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
344 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
345 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
353 def set_diskcheck(list):
354 for dc in diskcheckers:
357 def diskcheck_types():
358 return map(lambda dc: dc.type, diskcheckers)
362 class EntryProxy(SCons.Util.Proxy):
363 def __get_abspath(self):
365 return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
366 entry.name + "_abspath")
368 def __get_filebase(self):
369 name = self.get().name
370 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
373 def __get_suffix(self):
374 name = self.get().name
375 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
378 def __get_file(self):
379 name = self.get().name
380 return SCons.Util.SpecialAttrWrapper(name, name + "_file")
382 def __get_base_path(self):
383 """Return the file's directory and file name, with the
386 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
387 entry.name + "_base")
389 def __get_posix_path(self):
390 """Return the path with / as the path separator,
391 regardless of platform."""
396 r = string.replace(entry.get_path(), os.sep, '/')
397 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_posix")
399 def __get_win32_path(self):
400 """Return the path with \ as the path separator,
401 regardless of platform."""
406 r = string.replace(entry.get_path(), os.sep, '\\')
407 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_win32")
409 def __get_srcnode(self):
410 return EntryProxy(self.get().srcnode())
412 def __get_srcdir(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().dir)
417 def __get_rsrcnode(self):
418 return EntryProxy(self.get().srcnode().rfile())
420 def __get_rsrcdir(self):
421 """Returns the directory containing the source node linked to this
422 node via BuildDir(), or the directory of this node if not linked."""
423 return EntryProxy(self.get().srcnode().rfile().dir)
426 return EntryProxy(self.get().dir)
428 dictSpecialAttrs = { "base" : __get_base_path,
429 "posix" : __get_posix_path,
430 "win32" : __get_win32_path,
431 "srcpath" : __get_srcnode,
432 "srcdir" : __get_srcdir,
434 "abspath" : __get_abspath,
435 "filebase" : __get_filebase,
436 "suffix" : __get_suffix,
438 "rsrcpath" : __get_rsrcnode,
439 "rsrcdir" : __get_rsrcdir,
442 def __getattr__(self, name):
443 # This is how we implement the "special" attributes
444 # such as base, posix, srcdir, etc.
446 return self.dictSpecialAttrs[name](self)
449 attr = SCons.Util.Proxy.__getattr__(self, name)
450 except AttributeError:
452 classname = string.split(str(entry.__class__), '.')[-1]
453 if classname[-2:] == "'>":
454 # new-style classes report their name as:
455 # "<class 'something'>"
456 # instead of the classic classes:
458 classname = classname[:-2]
459 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
462 class Base(SCons.Node.Node):
463 """A generic class for file system entries. This class is for
464 when we don't know yet whether the entry being looked up is a file
465 or a directory. Instances of this class can morph into either
466 Dir or File objects by a later, more precise lookup.
468 Note: this class does not define __cmp__ and __hash__ for
469 efficiency reasons. SCons does a lot of comparing of
470 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
471 as fast as possible, which means we want to use Python's built-in
472 object identity comparisons.
475 def __init__(self, name, directory, fs):
476 """Initialize a generic Node.FS.Base object.
478 Call the superclass initialization, take care of setting up
479 our relative and absolute paths, identify our parent
480 directory, and indicate that this node should use
482 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
483 SCons.Node.Node.__init__(self)
488 assert directory, "A directory must be provided"
490 self.abspath = directory.entry_abspath(name)
491 if directory.path == '.':
494 self.path = directory.entry_path(name)
495 if directory.tpath == '.':
498 self.tpath = directory.entry_tpath(name)
499 self.path_elements = directory.path_elements + [self]
502 self.cwd = None # will hold the SConscript directory for target nodes
503 self.duplicate = directory.duplicate
506 """Completely clear a Node.FS.Base object of all its cached
507 state (so that it can be re-evaluated by interfaces that do
508 continuous integration builds).
511 SCons.Node.Node.clear(self)
516 def get_suffix(self):
518 return SCons.Util.splitext(self.name)[1]
524 """A Node.FS.Base object's string representation is its path
528 return self._save_str()
529 return self._get_str()
533 return self._get_str()
536 if self.duplicate or self.is_derived():
537 return self.get_path()
538 return self.srcnode().get_path()
544 try: return self.fs.stat(self.abspath)
545 except os.error: return None
549 return not self.stat() is None
553 return self.rfile().exists()
558 return self.stat()[stat.ST_MTIME]
565 return self.stat()[stat.ST_SIZE]
571 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
575 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
577 if hasattr(os, 'symlink'):
579 try: st = self.fs.lstat(self.abspath)
580 except os.error: return 0
581 return stat.S_ISLNK(st[stat.ST_MODE])
584 return 0 # no symlinks
586 def is_under(self, dir):
590 return self.dir.is_under(dir)
596 """If this node is in a build path, return the node
597 corresponding to its source file. Otherwise, return
604 srcnode = self.fs.Entry(name, dir.srcdir,
605 klass=self.__class__)
607 name = dir.name + os.sep + name
611 def get_path(self, dir=None):
612 """Return path relative to the current working directory of the
613 Node.FS.Base object that owns us."""
615 dir = self.fs.getcwd()
618 path_elems = self.path_elements
619 try: i = path_elems.index(dir)
620 except ValueError: pass
621 else: path_elems = path_elems[i+1:]
622 path_elems = map(lambda n: n.name, path_elems)
623 return string.join(path_elems, os.sep)
625 def set_src_builder(self, builder):
626 """Set the source code builder for this node."""
627 self.sbuilder = builder
628 if not self.has_builder():
629 self.builder_set(builder)
631 def src_builder(self):
632 """Fetch the source code builder for this node.
634 If there isn't one, we cache the source code builder specified
635 for the directory (which in turn will cache the value from its
636 parent directory, and so on up to the file system root).
640 except AttributeError:
641 scb = self.dir.src_builder()
645 def get_abspath(self):
646 """Get the absolute path of the file."""
649 def for_signature(self):
650 # Return just our name. Even an absolute path would not work,
651 # because that can change thanks to symlinks or remapped network
655 def get_subst_proxy(self):
658 except AttributeError:
659 ret = EntryProxy(self)
664 """This is the class for generic Node.FS entries--that is, things
665 that could be a File or a Dir, but we're just not sure yet.
666 Consequently, the methods in this class really exist just to
667 transform their associated object into the right class when the
668 time comes, and then call the same-named method in the transformed
671 def diskcheck_match(self):
674 def disambiguate(self):
679 self.__class__ = File
685 """We're a generic Entry, but the caller is actually looking for
686 a File at this point, so morph into one."""
687 self.__class__ = File
690 return File.rfile(self)
692 def get_found_includes(self, env, scanner, path):
693 """If we're looking for included files, it's because this Entry
694 is really supposed to be a File itself."""
695 return self.disambiguate().get_found_includes(env, scanner, path)
697 def scanner_key(self):
698 return self.get_suffix()
700 def get_contents(self):
701 """Fetch the contents of the entry.
703 Since this should return the real contents from the file
704 system, we check to see into what sort of subclass we should
707 self.__class__ = File
709 return self.get_contents()
713 return self.get_contents()
715 return '' # avoid errors for dangling symlinks
718 def rel_path(self, other):
719 return self.disambiguate().rel_path(other)
722 """Return if the Entry exists. Check the file system to see
723 what we should turn into first. Assume a file if there's no
725 return self.disambiguate().exists()
727 def calc_signature(self, calc=None):
728 """Return the Entry's calculated signature. Check the file
729 system to see what we should turn into first. Assume a file if
730 there's no directory."""
731 return self.disambiguate().calc_signature(calc)
733 def must_be_a_Dir(self):
734 """Called to make sure a Node is a Dir. Since we're an
735 Entry, we can morph into one."""
740 # This is for later so we can differentiate between Entry the class and Entry
741 # the method of the FS class.
747 if SCons.Memoize.use_memoizer:
748 __metaclass__ = SCons.Memoize.Memoized_Metaclass
750 # This class implements an abstraction layer for operations involving
751 # a local file system. Essentially, this wraps any function in
752 # the os, os.path or shutil modules that we use to actually go do
753 # anything with or to the local file system.
755 # Note that there's a very good chance we'll refactor this part of
756 # the architecture in some way as we really implement the interface(s)
757 # for remote file system Nodes. For example, the right architecture
758 # might be to have this be a subclass instead of a base class.
759 # Nevertheless, we're using this as a first step in that direction.
761 # We're not using chdir() yet because the calling subclass method
762 # needs to use os.chdir() directly to avoid recursion. Will we
763 # really need this one?
764 #def chdir(self, path):
765 # return os.chdir(path)
766 def chmod(self, path, mode):
767 return os.chmod(path, mode)
768 def copy2(self, src, dst):
769 return shutil.copy2(src, dst)
770 def exists(self, path):
771 return os.path.exists(path)
772 def getmtime(self, path):
773 return os.path.getmtime(path)
774 def getsize(self, path):
775 return os.path.getsize(path)
776 def isdir(self, path):
777 return os.path.isdir(path)
778 def isfile(self, path):
779 return os.path.isfile(path)
780 def link(self, src, dst):
781 return os.link(src, dst)
782 def lstat(self, path):
783 return os.lstat(path)
784 def listdir(self, path):
785 return os.listdir(path)
786 def makedirs(self, path):
787 return os.makedirs(path)
788 def mkdir(self, path):
789 return os.mkdir(path)
790 def rename(self, old, new):
791 return os.rename(old, new)
792 def stat(self, path):
794 def symlink(self, src, dst):
795 return os.symlink(src, dst)
796 def open(self, path):
798 def unlink(self, path):
799 return os.unlink(path)
801 if hasattr(os, 'symlink'):
802 def islink(self, path):
803 return os.path.islink(path)
805 def islink(self, path):
806 return 0 # no symlinks
808 if SCons.Memoize.use_old_memoization():
810 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
811 def __init__(self, *args, **kw):
812 apply(_FSBase.__init__, (self,)+args, kw)
813 SCons.Memoize.Memoizer.__init__(self)
817 # # Skeleton for the obvious methods we might need from the
818 # # abstraction layer for a remote filesystem.
819 # def upload(self, local_src, remote_dst):
821 # def download(self, remote_src, local_dst):
827 def __init__(self, path = None):
828 """Initialize the Node.FS subsystem.
830 The supplied path is the top of the source tree, where we
831 expect to find the top-level build file. If no path is
832 supplied, the current directory is the default.
834 The path argument must be a valid absolute path.
836 if __debug__: logInstanceCreation(self, 'Node.FS')
838 self.SConstruct_dir = None
839 self.CachePath = None
840 self.cache_force = None
841 self.cache_show = None
842 self.max_drift = default_max_drift
845 self.pathTop = os.getcwd()
849 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
854 def clear_cache(self):
858 def set_SConstruct_dir(self, dir):
859 self.SConstruct_dir = dir
861 def get_max_drift(self):
862 return self.max_drift
864 def set_max_drift(self, max_drift):
865 self.max_drift = max_drift
870 def __checkClass(self, node, klass):
871 if isinstance(node, klass) or klass == Entry:
873 if node.__class__ == Entry:
874 node.__class__ = klass
877 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
878 (node.__class__.__name__, node.path, klass.__name__)
880 def _doLookup(self, fsclass, name, directory = None, create = 1):
881 """This method differs from the File and Dir factory methods in
882 one important way: the meaning of the directory parameter.
883 In this method, if directory is None or not supplied, the supplied
884 name is expected to be an absolute path. If you try to look up a
885 relative path with directory=None, then an AssertionError will be
890 # This is a stupid hack to compensate for the fact that
891 # the POSIX and Win32 versions of os.path.normpath() behave
892 # differently in older versions of Python. In particular,
894 # os.path.normpath('./') == '.'
896 # os.path.normpath('./') == ''
897 # os.path.normpath('.\\') == ''
899 # This is a definite bug in the Python library, but we have
902 path_orig = string.split(name, os.sep)
903 path_norm = string.split(_my_normcase(name), os.sep)
905 first_orig = path_orig.pop(0) # strip first element
906 first_norm = path_norm.pop(0) # strip first element
908 drive, path_first = os.path.splitdrive(first_orig)
910 path_orig = [ path_first, ] + path_orig
911 path_norm = [ _my_normcase(path_first), ] + path_norm
913 drive = _my_normcase(drive)
916 directory = self.Root[drive]
919 raise SCons.Errors.UserError
920 directory = RootDir(drive, self)
921 self.Root[drive] = directory
926 last_orig = path_orig.pop() # strip last element
927 last_norm = path_norm.pop() # strip last element
929 # Lookup the directory
930 for orig, norm in map(None, path_orig, path_norm):
932 entries = directory.entries
933 except AttributeError:
934 # We tried to look up the entry in either an Entry or
935 # a File. Give whatever it is a chance to do what's
936 # appropriate: morph into a Dir or raise an exception.
937 directory.must_be_a_Dir()
938 entries = directory.entries
940 directory = entries[norm]
943 raise SCons.Errors.UserError
945 d = Dir(orig, directory, self)
947 # Check the file system (or not, as configured) to make
948 # sure there isn't already a file there.
951 directory.entries[norm] = d
952 directory.add_wkid(d)
955 directory.must_be_a_Dir()
958 e = directory.entries[last_norm]
961 raise SCons.Errors.UserError
963 result = fsclass(last_orig, directory, self)
965 # Check the file system (or not, as configured) to make
966 # sure there isn't already a directory at the path on
967 # disk where we just created a File node, and vice versa.
968 result.diskcheck_match()
970 directory.entries[last_norm] = result
971 directory.add_wkid(result)
973 result = self.__checkClass(e, fsclass)
976 def _transformPath(self, name, directory):
977 """Take care of setting up the correct top-level directory,
978 usually in preparation for a call to doLookup().
980 If the path name is prepended with a '#', then it is unconditionally
981 interpreted as relative to the top-level directory of this FS.
983 If directory is None, and name is a relative path,
984 then the same applies.
986 if name and name[0] == '#':
989 if name and (name[0] == os.sep or name[0] == '/'):
990 # Correct such that '#/foo' is equivalent
993 name = os.path.join('.', os.path.normpath(name))
995 directory = self._cwd
996 return (os.path.normpath(name), directory)
998 def chdir(self, dir, change_os_dir=0):
999 """Change the current working directory for lookups.
1000 If change_os_dir is true, we will also change the "real" cwd
1008 os.chdir(dir.abspath)
1013 def Entry(self, name, directory = None, create = 1, klass=None):
1014 """Lookup or create a generic Entry node with the specified name.
1015 If the name is a relative path (begins with ./, ../, or a file
1016 name), then it is looked up relative to the supplied directory
1017 node, or to the top level directory of the FS (supplied at
1018 construction time) if no directory is supplied.
1024 if isinstance(name, Base):
1025 return self.__checkClass(name, klass)
1027 if directory and not isinstance(directory, Dir):
1028 directory = self.Dir(directory)
1029 name, directory = self._transformPath(name, directory)
1030 return self._doLookup(klass, name, directory, create)
1032 def File(self, name, directory = None, create = 1):
1033 """Lookup or create a File node with the specified name. If
1034 the name is a relative path (begins with ./, ../, or a file name),
1035 then it is looked up relative to the supplied directory node,
1036 or to the top level directory of the FS (supplied at construction
1037 time) if no directory is supplied.
1039 This method will raise TypeError if a directory is found at the
1043 return self.Entry(name, directory, create, File)
1045 def Dir(self, name, directory = None, create = 1):
1046 """Lookup or create a Dir node with the specified name. If
1047 the name is a relative path (begins with ./, ../, or a file name),
1048 then it is looked up relative to the supplied directory node,
1049 or to the top level directory of the FS (supplied at construction
1050 time) if no directory is supplied.
1052 This method will raise TypeError if a normal file is found at the
1056 return self.Entry(name, directory, create, Dir)
1058 def BuildDir(self, build_dir, src_dir, duplicate=1):
1059 """Link the supplied build directory to the source directory
1060 for purposes of building files."""
1062 if not isinstance(src_dir, SCons.Node.Node):
1063 src_dir = self.Dir(src_dir)
1064 if not isinstance(build_dir, SCons.Node.Node):
1065 build_dir = self.Dir(build_dir)
1066 if src_dir.is_under(build_dir):
1067 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1068 if build_dir.srcdir:
1069 if build_dir.srcdir == src_dir:
1070 return # We already did this.
1071 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1072 build_dir.link(src_dir, duplicate)
1074 def Repository(self, *dirs):
1075 """Specify Repository directories to search."""
1077 if not isinstance(d, SCons.Node.Node):
1079 self.Top.addRepository(d)
1081 def Rfindalldirs(self, pathlist, cwd):
1083 if SCons.Util.is_String(pathlist):
1084 pathlist = string.split(pathlist, os.pathsep)
1085 if not SCons.Util.is_List(pathlist):
1086 pathlist = [pathlist]
1088 for path in filter(None, pathlist):
1089 if isinstance(path, SCons.Node.Node):
1092 path, dir = self._transformPath(path, cwd)
1094 result.extend(dir.get_all_rdirs())
1097 def CacheDir(self, path):
1098 self.CachePath = path
1100 def build_dir_target_climb(self, orig, dir, tail):
1101 """Create targets in corresponding build directories
1103 Climb the directory tree, and look up path names
1104 relative to any linked build directories we find.
1109 fmt = "building associated BuildDir targets: %s"
1112 for bd in dir.build_dirs:
1113 if start_dir.is_under(bd):
1114 # If already in the build-dir location, don't reflect
1115 return [orig], fmt % str(orig)
1116 p = apply(os.path.join, [bd.path] + tail)
1117 targets.append(self.Entry(p))
1118 tail = [dir.name] + tail
1121 message = fmt % string.join(map(str, targets))
1122 return targets, message
1125 """A class for directories in a file system.
1128 def __init__(self, name, directory, fs):
1129 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1130 Base.__init__(self, name, directory, fs)
1134 """Turn a file system Node (either a freshly initialized directory
1135 object or a separate Entry object) into a proper directory object.
1137 Set up this directory's entries and hook it into the file
1138 system tree. Specify that directories (this Node) don't use
1139 signatures for calculating whether they're current.
1142 self.repositories = []
1146 self.entries['.'] = self
1147 self.entries['..'] = self.dir
1150 self._sconsign = None
1151 self.build_dirs = []
1153 # Don't just reset the executor, replace its action list,
1154 # because it might have some pre-or post-actions that need to
1156 self.builder = get_MkdirBuilder()
1157 self.get_executor().set_action_list(self.builder.action)
1159 def diskcheck_match(self):
1160 diskcheck_match(self, self.fs.isfile,
1161 "File %s found where directory expected.")
1163 def disambiguate(self):
1166 def __clearRepositoryCache(self, duplicate=None):
1167 """Called when we change the repository(ies) for a directory.
1168 This clears any cached information that is invalidated by changing
1171 for node in self.entries.values():
1172 if node != self.dir:
1173 if node != self and isinstance(node, Dir):
1174 node.__clearRepositoryCache(duplicate)
1179 except AttributeError:
1181 if duplicate != None:
1182 node.duplicate=duplicate
1184 def __resetDuplicate(self, node):
1186 node.duplicate = node.get_dir().duplicate
1188 def Entry(self, name):
1189 """Create an entry node named 'name' relative to this directory."""
1190 return self.fs.Entry(name, self)
1192 def Dir(self, name):
1193 """Create a directory node named 'name' relative to this directory."""
1194 return self.fs.Dir(name, self)
1196 def File(self, name):
1197 """Create a file node named 'name' relative to this directory."""
1198 return self.fs.File(name, self)
1200 def link(self, srcdir, duplicate):
1201 """Set this directory as the build directory for the
1202 supplied source directory."""
1203 self.srcdir = srcdir
1204 self.duplicate = duplicate
1205 self.__clearRepositoryCache(duplicate)
1206 srcdir.build_dirs.append(self)
1208 def getRepositories(self):
1209 """Returns a list of repositories for this directory.
1211 if self.srcdir and not self.duplicate:
1212 return self.srcdir.get_all_rdirs() + self.repositories
1213 return self.repositories
1215 def get_all_rdirs(self):
1221 for rep in dir.getRepositories():
1222 result.append(rep.Dir(fname))
1223 fname = dir.name + os.sep + fname
1227 def addRepository(self, dir):
1228 if dir != self and not dir in self.repositories:
1229 self.repositories.append(dir)
1231 self.__clearRepositoryCache()
1234 return self.entries['..']
1236 def rel_path(self, other):
1237 """Return a path to "other" relative to this directory.
1239 if isinstance(other, Dir):
1245 except AttributeError:
1248 return name and name[0] or '.'
1250 for x, y in map(None, self.path_elements, other.path_elements):
1254 path_elems = ['..']*(len(self.path_elements)-i) \
1255 + map(lambda n: n.name, other.path_elements[i:]) \
1258 return string.join(path_elems, os.sep)
1261 if not self.implicit is None:
1264 self.implicit_dict = {}
1265 self._children_reset()
1267 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1268 deps = filter(dont_scan, self.entries.keys())
1269 # keys() is going to give back the entries in an internal,
1270 # unsorted order. Sort 'em so the order is deterministic.
1272 entries = map(lambda n, e=self.entries: e[n], deps)
1274 self._add_child(self.implicit, self.implicit_dict, entries)
1276 def get_found_includes(self, env, scanner, path):
1277 """Return the included implicit dependencies in this file.
1278 Cache results so we only scan the file once per path
1279 regardless of how many times this information is requested.
1283 # Clear cached info for this Node. If we already visited this
1284 # directory on our walk down the tree (because we didn't know at
1285 # that point it was being used as the source for another Node)
1286 # then we may have calculated build signature before realizing
1287 # we had to scan the disk. Now that we have to, though, we need
1288 # to invalidate the old calculated signature so that any node
1289 # dependent on our directory structure gets one that includes
1290 # info about everything on disk.
1292 return scanner(self, env, path)
1294 def build(self, **kw):
1295 """A null "builder" for directories."""
1297 if not self.builder is MkdirBuilder:
1298 apply(SCons.Node.Node.build, [self,], kw)
1301 """Create this directory, silently and without worrying about
1302 whether the builder is the default or not."""
1308 listDirs.append(parent)
1311 raise SCons.Errors.StopError, parent.path
1314 for dirnode in listDirs:
1316 # Don't call dirnode.build(), call the base Node method
1317 # directly because we definitely *must* create this
1318 # directory. The dirnode.build() method will suppress
1319 # the build if it's the default builder.
1320 SCons.Node.Node.build(dirnode)
1321 dirnode.get_executor().nullify()
1322 # The build() action may or may not have actually
1323 # created the directory, depending on whether the -n
1324 # option was used or not. Delete the _exists and
1325 # _rexists attributes so they can be reevaluated.
1330 def multiple_side_effect_has_builder(self):
1332 return not self.builder is MkdirBuilder and self.has_builder()
1334 def alter_targets(self):
1335 """Return any corresponding targets in a build directory.
1337 return self.fs.build_dir_target_climb(self, self, [])
1339 def scanner_key(self):
1340 """A directory does not get scanned."""
1343 def get_contents(self):
1344 """Return aggregate contents of all our children."""
1345 contents = cStringIO.StringIO()
1346 for kid in self.children():
1347 contents.write(kid.get_contents())
1348 return contents.getvalue()
1353 def do_duplicate(self, src):
1356 def current(self, calc=None):
1357 """If all of our children were up-to-date, then this
1358 directory was up-to-date, too."""
1359 if not self.builder is MkdirBuilder and not self.exists():
1362 for kid in self.children():
1364 if s and (not state or s > state):
1367 if state == 0 or state == SCons.Node.up_to_date:
1374 if not self.exists():
1375 norm_name = _my_normcase(self.name)
1376 for dir in self.dir.get_all_rdirs():
1377 try: node = dir.entries[norm_name]
1378 except KeyError: node = dir.dir_on_disk(self.name)
1379 if node and node.exists() and \
1380 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1385 """Return the .sconsign file info for this directory,
1386 creating it first if necessary."""
1387 if not self._sconsign:
1388 import SCons.SConsign
1389 self._sconsign = SCons.SConsign.ForDirectory(self)
1390 return self._sconsign
1393 """Dir has a special need for srcnode()...if we
1394 have a srcdir attribute set, then that *is* our srcnode."""
1397 return Base.srcnode(self)
1399 def get_timestamp(self):
1400 """Return the latest timestamp from among our children"""
1402 for kid in self.children():
1403 if kid.get_timestamp() > stamp:
1404 stamp = kid.get_timestamp()
1407 def entry_abspath(self, name):
1408 return self.abspath + os.sep + name
1410 def entry_path(self, name):
1411 return self.path + os.sep + name
1413 def entry_tpath(self, name):
1414 return self.tpath + os.sep + name
1416 def must_be_a_Dir(self):
1417 """Called to make sure a Node is a Dir. Since we're already
1418 one, this is a no-op for us."""
1421 def entry_exists_on_disk(self, name):
1423 return self.fs.exists(self.entry_abspath(name))
1425 def srcdir_list(self):
1433 d = dir.srcdir.Dir(dirname)
1435 # Shouldn't source from something in the build path:
1436 # build_dir is probably under src_dir, in which case
1437 # we are reflecting.
1440 dirname = dir.name + os.sep + dirname
1445 def srcdir_duplicate(self, name):
1446 for dir in self.srcdir_list():
1447 if dir.entry_exists_on_disk(name):
1448 srcnode = dir.File(name)
1450 node = self.File(name)
1451 node.do_duplicate(srcnode)
1457 def srcdir_find_file(self, filename):
1460 if (isinstance(node, File) or isinstance(node, Entry)) and \
1461 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1465 norm_name = _my_normcase(filename)
1467 for rdir in self.get_all_rdirs():
1468 try: node = rdir.entries[norm_name]
1469 except KeyError: node = rdir.file_on_disk(filename)
1470 else: node = func(node)
1474 for srcdir in self.srcdir_list():
1475 for rdir in srcdir.get_all_rdirs():
1476 try: node = rdir.entries[norm_name]
1477 except KeyError: node = rdir.file_on_disk(filename)
1478 else: node = func(node)
1480 return File(filename, self, self.fs), srcdir
1484 def dir_on_disk(self, name):
1485 if self.entry_exists_on_disk(name):
1486 try: return self.Dir(name)
1487 except TypeError: pass
1490 def file_on_disk(self, name):
1491 if self.entry_exists_on_disk(name) or \
1492 diskcheck_rcs(self, name) or \
1493 diskcheck_sccs(self, name):
1494 try: return self.File(name)
1495 except TypeError: pass
1496 return self.srcdir_duplicate(name)
1499 """A class for the root directory of a file system.
1501 This is the same as a Dir class, except that the path separator
1502 ('/' or '\\') is actually part of the name, so we don't need to
1503 add a separator when creating the path names of entries within
1506 def __init__(self, name, fs):
1507 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1508 # We're going to be our own parent directory (".." entry and .dir
1509 # attribute) so we have to set up some values so Base.__init__()
1510 # won't gag won't it calls some of our methods.
1514 self.path_elements = []
1516 Base.__init__(self, name, self, fs)
1518 # Now set our paths to what we really want them to be: the
1519 # initial drive letter (the name) plus the directory separator.
1520 self.abspath = name + os.sep
1521 self.path = name + os.sep
1522 self.tpath = name + os.sep
1528 def entry_abspath(self, name):
1529 return self.abspath + name
1531 def entry_path(self, name):
1532 return self.path + name
1534 def entry_tpath(self, name):
1535 return self.tpath + name
1537 def is_under(self, dir):
1549 def src_builder(self):
1552 class NodeInfo(SCons.Node.NodeInfo):
1553 # The bsig attributes needs to stay here, if it's initialized in
1554 # __init__() then the assignment seems to overwrite any values
1555 # unpickled from .sconsign files.
1557 def __cmp__(self, other):
1558 return cmp(self.bsig, other.bsig)
1559 def update(self, node):
1560 self.timestamp = node.get_timestamp()
1561 self.size = node.getsize()
1563 class BuildInfo(SCons.Node.BuildInfo):
1564 def __init__(self, node):
1565 SCons.Node.BuildInfo.__init__(self, node)
1567 def convert_to_sconsign(self):
1568 """Convert this BuildInfo object for writing to a .sconsign file
1570 We hung onto the node that we refer to so that we can translate
1571 the lists of bsources, bdepends and bimplicit Nodes into strings
1572 relative to the node, but we don't want to write out that Node
1573 itself to the .sconsign file, so we delete the attribute in
1576 rel_path = self.node.rel_path
1577 delattr(self, 'node')
1578 for attr in ['bsources', 'bdepends', 'bimplicit']:
1580 val = getattr(self, attr)
1581 except AttributeError:
1584 setattr(self, attr, map(rel_path, val))
1585 def convert_from_sconsign(self, dir, name):
1586 """Convert a newly-read BuildInfo object for in-SCons use
1588 An on-disk BuildInfo comes without a reference to the node
1589 for which it's intended, so we have to convert the arguments
1590 and add back a self.node attribute. The bsources, bdepends and
1591 bimplicit lists all come from disk as paths relative to that node,
1592 so convert them to actual Nodes for use by the rest of SCons.
1594 self.node = dir.Entry(name)
1595 Entry_func = self.node.dir.Entry
1596 for attr in ['bsources', 'bdepends', 'bimplicit']:
1598 val = getattr(self, attr)
1599 except AttributeError:
1602 setattr(self, attr, map(Entry_func, val))
1605 """A class for files in a file system.
1607 def diskcheck_match(self):
1608 diskcheck_match(self, self.fs.isdir,
1609 "Directory %s found where file expected.")
1611 def __init__(self, name, directory, fs):
1612 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1613 Base.__init__(self, name, directory, fs)
1616 def Entry(self, name):
1617 """Create an entry node named 'name' relative to
1618 the SConscript directory of this file."""
1619 return self.fs.Entry(name, self.cwd)
1621 def Dir(self, name):
1622 """Create a directory node named 'name' relative to
1623 the SConscript directory of this file."""
1624 return self.fs.Dir(name, self.cwd)
1626 def Dirs(self, pathlist):
1627 """Create a list of directories relative to the SConscript
1628 directory of this file."""
1629 return map(lambda p, s=self: s.Dir(p), pathlist)
1631 def File(self, name):
1632 """Create a file node named 'name' relative to
1633 the SConscript directory of this file."""
1634 return self.fs.File(name, self.cwd)
1636 def RDirs(self, pathlist):
1637 """Search for a list of directories in the Repository list."""
1638 return self.fs.Rfindalldirs(pathlist, self.cwd)
1641 """Turn a file system node into a File object. __cache_reset__"""
1642 self.scanner_paths = {}
1643 if not hasattr(self, '_local'):
1646 def disambiguate(self):
1649 def scanner_key(self):
1650 return self.get_suffix()
1652 def get_contents(self):
1653 if not self.rexists():
1655 return open(self.rfile().abspath, "rb").read()
1657 def get_timestamp(self):
1659 return self.rfile().getmtime()
1663 def store_info(self, obj):
1664 # Merge our build information into the already-stored entry.
1665 # This accomodates "chained builds" where a file that's a target
1666 # in one build (SConstruct file) is a source in a different build.
1667 # See test/chained-build.py for the use case.
1668 entry = self.get_stored_info()
1670 self.dir.sconsign().set_entry(self.name, entry)
1672 def get_stored_info(self):
1675 stored = self.dir.sconsign().get_entry(self.name)
1676 except (KeyError, OSError):
1677 return self.new_binfo()
1679 if not hasattr(stored, 'ninfo'):
1680 # Transition: The .sconsign file entry has no NodeInfo
1681 # object, which means it's a slightly older BuildInfo.
1682 # Copy over the relevant attributes.
1683 ninfo = stored.ninfo = self.new_ninfo()
1684 for attr in ninfo.__dict__.keys():
1686 setattr(ninfo, attr, getattr(stored, attr))
1687 except AttributeError:
1691 def get_stored_implicit(self):
1692 binfo = self.get_stored_info()
1693 try: return binfo.bimplicit
1694 except AttributeError: return None
1696 def rel_path(self, other):
1697 return self.dir.rel_path(other)
1699 def get_found_includes(self, env, scanner, path):
1700 """Return the included implicit dependencies in this file.
1701 Cache results so we only scan the file once per path
1702 regardless of how many times this information is requested.
1706 return scanner(self, env, path)
1708 def _createDir(self):
1709 # ensure that the directories for this node are
1713 def retrieve_from_cache(self):
1714 """Try to retrieve the node's content from a cache
1716 This method is called from multiple threads in a parallel build,
1717 so only do thread safe stuff here. Do thread unsafe stuff in
1720 Note that there's a special trick here with the execute flag
1721 (one that's not normally done for other actions). Basically
1722 if the user requested a noexec (-n) build, then
1723 SCons.Action.execute_actions is set to 0 and when any action
1724 is called, it does its showing but then just returns zero
1725 instead of actually calling the action execution operation.
1726 The problem for caching is that if the file does NOT exist in
1727 cache then the CacheRetrieveString won't return anything to
1728 show for the task, but the Action.__call__ won't call
1729 CacheRetrieveFunc; instead it just returns zero, which makes
1730 the code below think that the file *was* successfully
1731 retrieved from the cache, therefore it doesn't do any
1732 subsequent building. However, the CacheRetrieveString didn't
1733 print anything because it didn't actually exist in the cache,
1734 and no more build actions will be performed, so the user just
1735 sees nothing. The fix is to tell Action.__call__ to always
1736 execute the CacheRetrieveFunc and then have the latter
1737 explicitly check SCons.Action.execute_actions itself.
1739 Returns true iff the node was successfully retrieved.
1741 b = self.is_derived()
1742 if not b and not self.has_src_builder():
1744 if b and self.fs.CachePath:
1745 if self.fs.cache_show:
1746 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1747 self.build(presub=0, execute=0)
1749 elif CacheRetrieve(self, [], None, execute=1) == 0:
1754 """Called just after this node is successfully built.
1756 # Push this file out to cache before the superclass Node.built()
1757 # method has a chance to clear the build signature, which it
1758 # will do if this file has a source scanner.
1759 if self.fs.CachePath and self.exists():
1760 CachePush(self, [], None)
1761 self.fs.clear_cache()
1762 SCons.Node.Node.built(self)
1765 if self.fs.CachePath and self.fs.cache_force and self.exists():
1766 CachePush(self, None, None)
1768 def has_src_builder(self):
1769 """Return whether this Node has a source builder or not.
1771 If this Node doesn't have an explicit source code builder, this
1772 is where we figure out, on the fly, if there's a transparent
1773 source code builder for it.
1775 Note that if we found a source builder, we also set the
1776 self.builder attribute, so that all of the methods that actually
1777 *build* this file don't have to do anything different.
1781 except AttributeError:
1785 scb = self.dir.src_builder()
1787 if diskcheck_sccs(self.dir, self.name):
1788 scb = get_DefaultSCCSBuilder()
1789 elif diskcheck_rcs(self.dir, self.name):
1790 scb = get_DefaultRCSBuilder()
1794 self.builder_set(scb)
1796 return not scb is None
1798 def alter_targets(self):
1799 """Return any corresponding targets in a build directory.
1801 if self.is_derived():
1803 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1805 def is_pseudo_derived(self):
1807 return self.has_src_builder()
1809 def _rmv_existing(self):
1811 Unlink(self, [], None)
1814 """Prepare for this file to be created."""
1815 SCons.Node.Node.prepare(self)
1817 if self.get_state() != SCons.Node.up_to_date:
1819 if self.is_derived() and not self.precious:
1820 self._rmv_existing()
1824 except SCons.Errors.StopError, drive:
1825 desc = "No drive `%s' for target `%s'." % (drive, self)
1826 raise SCons.Errors.StopError, desc
1829 """Remove this file."""
1830 if self.exists() or self.islink():
1831 self.fs.unlink(self.path)
1835 def do_duplicate(self, src):
1838 Unlink(self, None, None)
1839 except SCons.Errors.BuildError:
1842 Link(self, src, None)
1843 except SCons.Errors.BuildError, e:
1844 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1845 raise SCons.Errors.StopError, desc
1847 # The Link() action may or may not have actually
1848 # created the file, depending on whether the -n
1849 # option was used or not. Delete the _exists and
1850 # _rexists attributes so they can be reevaluated.
1855 # Duplicate from source path if we are set up to do this.
1856 if self.duplicate and not self.is_derived() and not self.linked:
1859 return Base.exists(self)
1861 if src.abspath != self.abspath and src.exists():
1862 self.do_duplicate(src)
1863 return Base.exists(self)
1866 # SIGNATURE SUBSYSTEM
1869 def new_binfo(self):
1870 return BuildInfo(self)
1872 def new_ninfo(self):
1877 def get_csig(self, calc=None):
1879 Generate a node's content signature, the digested signature
1883 cache - alternate node to use for the signature cache
1884 returns - the content signature
1887 return self.binfo.ninfo.csig
1888 except AttributeError:
1892 calc = self.calculator()
1894 max_drift = self.fs.max_drift
1895 mtime = self.get_timestamp()
1896 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
1900 old = self.get_stored_info().ninfo
1902 if old.timestamp and old.csig and old.timestamp == mtime:
1904 except AttributeError:
1907 csig = calc.module.signature(self)
1909 binfo = self.get_binfo()
1915 self.store_info(binfo)
1923 def current(self, calc=None):
1924 self.binfo = self.gen_binfo(calc)
1928 if self.always_build:
1930 if not self.exists():
1931 # The file doesn't exist locally...
1934 # ...but there is one in a Repository...
1935 old = r.get_stored_info()
1936 new = self.get_binfo()
1938 # ...and it's even up-to-date...
1940 # ...and they'd like a local copy.
1941 LocalCopy(self, r, None)
1942 self.store_info(new)
1946 old = self.get_stored_info()
1947 new = self.get_binfo()
1952 if not self.exists():
1953 norm_name = _my_normcase(self.name)
1954 for dir in self.dir.get_all_rdirs():
1955 try: node = dir.entries[norm_name]
1956 except KeyError: node = dir.file_on_disk(self.name)
1957 if node and node.exists() and \
1958 (isinstance(node, File) or isinstance(node, Entry) \
1959 or not node.is_derived()):
1964 return str(self.rfile())
1966 def cachepath(self):
1967 if not self.fs.CachePath:
1969 ninfo = self.get_binfo().ninfo
1970 if not hasattr(ninfo, 'bsig'):
1971 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
1972 elif ninfo.bsig is None:
1973 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1974 # Add the path to the cache signature, because multiple
1975 # targets built by the same action will all have the same
1976 # build signature, and we have to differentiate them somehow.
1977 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
1978 subdir = string.upper(cache_sig[0])
1979 dir = os.path.join(self.fs.CachePath, subdir)
1980 return dir, os.path.join(dir, cache_sig)
1982 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1983 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1985 def must_be_a_Dir(self):
1986 """Called to make sure a Node is a Dir. Since we're already a
1987 File, this is a TypeError..."""
1988 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1992 def find_file(filename, paths, verbose=None):
1994 find_file(str, [Dir()]) -> [nodes]
1996 filename - a filename to find
1997 paths - a list of directory path *nodes* to search in. Can be
1998 represented as a list, a tuple, or a callable that is
1999 called with no arguments and returns the list or tuple.
2001 returns - the node created from the found file.
2003 Find a node corresponding to either a derived file or a file
2004 that exists already.
2006 Only the first file found is returned, and none is returned
2007 if no file is found.
2011 if not SCons.Util.is_String(verbose):
2012 verbose = "find_file"
2013 if not callable(verbose):
2014 verbose = ' %s: ' % verbose
2015 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2017 verbose = lambda x: x
2022 # Give Entries a chance to morph into Dirs.
2023 paths = map(lambda p: p.must_be_a_Dir(), paths)
2025 filedir, filename = os.path.split(filename)
2027 def filedir_lookup(p, fd=filedir):
2031 # We tried to look up a Dir, but it seems there's already
2032 # a File (or something else) there. No big.
2034 paths = filter(None, map(filedir_lookup, paths))
2037 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2038 node, d = dir.srcdir_find_file(filename)
2040 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2044 def find_files(filenames, paths):
2046 find_files([str], [Dir()]) -> [nodes]
2048 filenames - a list of filenames to find
2049 paths - a list of directory path *nodes* to search in
2051 returns - the nodes created from the found files.
2053 Finds nodes corresponding to either derived files or files
2056 Only the first file found is returned for each filename,
2057 and any files that aren't found are ignored.
2059 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2060 return filter(None, nodes)