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__"
39 from itertools import izip
51 from SCons.Debug import logInstanceCreation
55 import SCons.Node.Alias
60 from SCons.Debug import Trace
64 # The max_drift value: by default, use a cached signature value for
65 # any file that's been untouched for more than two days.
66 default_max_drift = 2*24*60*60
69 # We stringify these file system Nodes a lot. Turning a file system Node
70 # into a string is non-trivial, because the final string representation
71 # can depend on a lot of factors: whether it's a derived target or not,
72 # whether it's linked to a repository or source directory, and whether
73 # there's duplication going on. The normal technique for optimizing
74 # calculations like this is to memoize (cache) the string value, so you
75 # only have to do the calculation once.
77 # A number of the above factors, however, can be set after we've already
78 # been asked to return a string for a Node, because a Repository() or
79 # VariantDir() call or the like may not occur until later in SConscript
80 # files. So this variable controls whether we bother trying to save
81 # string values for Nodes. The wrapper interface can set this whenever
82 # they're done mucking with Repository and VariantDir and the other stuff,
83 # to let this module know it can start returning saved string values
88 def save_strings(val):
93 # Avoid unnecessary function calls by recording a Boolean value that
94 # tells us whether or not os.path.splitdrive() actually does anything
95 # on this system, and therefore whether we need to bother calling it
96 # when looking up path names in various methods below.
101 def initialize_do_splitdrive():
103 drive, path = os.path.splitdrive('X:/foo')
104 do_splitdrive = not not drive
106 initialize_do_splitdrive()
110 needs_normpath_check = None
112 def initialize_normpath_check():
114 Initialize the normpath_check regular expression.
116 This function is used by the unit tests to re-initialize the pattern
117 when testing for behavior with different values of os.sep.
119 global needs_normpath_check
121 pattern = r'.*/|\.$|\.\.$'
123 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
124 needs_normpath_check = re.compile(pattern)
126 initialize_normpath_check()
129 # SCons.Action objects for interacting with the outside world.
131 # The Node.FS methods in this module should use these actions to
132 # create and/or remove files and directories; they should *not* use
133 # os.{link,symlink,unlink,mkdir}(), etc., directly.
135 # Using these SCons.Action objects ensures that descriptions of these
136 # external activities are properly displayed, that the displays are
137 # suppressed when the -s (silent) option is used, and (most importantly)
138 # the actions are disabled when the the -n option is used, in which case
139 # there should be *no* changes to the external file system(s)...
142 if hasattr(os, 'link'):
143 def _hardlink_func(fs, src, dst):
144 # If the source is a symlink, we can't just hard-link to it
145 # because a relative symlink may point somewhere completely
146 # different. We must disambiguate the symlink and then
147 # hard-link the final destination file.
148 while fs.islink(src):
149 link = fs.readlink(src)
150 if not os.path.isabs(link):
153 src = os.path.join(os.path.dirname(src), link)
156 _hardlink_func = None
158 if hasattr(os, 'symlink'):
159 def _softlink_func(fs, src, dst):
162 _softlink_func = None
164 def _copy_func(fs, src, dest):
165 shutil.copy2(src, dest)
167 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
170 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
171 'hard-copy', 'soft-copy', 'copy']
173 Link_Funcs = [] # contains the callables of the specified duplication style
175 def set_duplicate(duplicate):
176 # Fill in the Link_Funcs list according to the argument
177 # (discarding those not available on the platform).
179 # Set up the dictionary that maps the argument names to the
180 # underlying implementations. We do this inside this function,
181 # not in the top-level module code, so that we can remap os.link
182 # and os.symlink for testing purposes.
184 'hard' : _hardlink_func,
185 'soft' : _softlink_func,
189 if not duplicate in Valid_Duplicates:
190 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
191 "should be in Valid_Duplicates")
194 for func in string.split(duplicate,'-'):
196 Link_Funcs.append(link_dict[func])
198 def LinkFunc(target, source, env):
199 # Relative paths cause problems with symbolic links, so
200 # we use absolute paths, which may be a problem for people
201 # who want to move their soft-linked src-trees around. Those
202 # people should use the 'hard-copy' mode, softlinks cannot be
203 # used for that; at least I have no idea how ...
204 src = source[0].abspath
205 dest = target[0].abspath
206 dir, file = os.path.split(dest)
207 if dir and not target[0].fs.isdir(dir):
210 # Set a default order of link functions.
211 set_duplicate('hard-soft-copy')
213 # Now link the files with the previously specified order.
214 for func in Link_Funcs:
218 except (IOError, OSError):
219 # An OSError indicates something happened like a permissions
220 # problem or an attempt to symlink across file-system
221 # boundaries. An IOError indicates something like the file
222 # not existing. In either case, keeping trying additional
223 # functions in the list and only raise an error if the last
225 if func == Link_Funcs[-1]:
226 # exception of the last link method (copy) are fatal
232 Link = SCons.Action.Action(LinkFunc, None)
233 def LocalString(target, source, env):
234 return 'Local copy of %s from %s' % (target[0], source[0])
236 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
238 def UnlinkFunc(target, source, env):
240 t.fs.unlink(t.abspath)
243 Unlink = SCons.Action.Action(UnlinkFunc, None)
245 def MkdirFunc(target, source, env):
248 t.fs.mkdir(t.abspath)
251 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
255 def get_MkdirBuilder():
257 if MkdirBuilder is None:
259 import SCons.Defaults
260 # "env" will get filled in by Executor.get_build_env()
261 # calling SCons.Defaults.DefaultEnvironment() when necessary.
262 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
266 target_scanner = SCons.Defaults.DirEntryScanner,
267 name = "MkdirBuilder")
275 DefaultSCCSBuilder = None
276 DefaultRCSBuilder = None
278 def get_DefaultSCCSBuilder():
279 global DefaultSCCSBuilder
280 if DefaultSCCSBuilder is None:
282 # "env" will get filled in by Executor.get_build_env()
283 # calling SCons.Defaults.DefaultEnvironment() when necessary.
284 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
285 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
287 name = "DefaultSCCSBuilder")
288 return DefaultSCCSBuilder
290 def get_DefaultRCSBuilder():
291 global DefaultRCSBuilder
292 if DefaultRCSBuilder is None:
294 # "env" will get filled in by Executor.get_build_env()
295 # calling SCons.Defaults.DefaultEnvironment() when necessary.
296 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
297 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
299 name = "DefaultRCSBuilder")
300 return DefaultRCSBuilder
302 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
303 _is_cygwin = sys.platform == "cygwin"
304 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
309 return string.upper(x)
314 def __init__(self, type, do, ignore):
320 self.__call__ = self.do
321 def set_ignore(self):
322 self.__call__ = self.ignore
324 if self.type in list:
329 def do_diskcheck_match(node, predicate, errorfmt):
332 # If calling the predicate() cached a None value from stat(),
333 # remove it so it doesn't interfere with later attempts to
334 # build this Node as we walk the DAG. (This isn't a great way
335 # to do this, we're reaching into an interface that doesn't
336 # really belong to us, but it's all about performance, so
337 # for now we'll just document the dependency...)
338 if node._memo['stat'] is None:
339 del node._memo['stat']
340 except (AttributeError, KeyError):
343 raise TypeError, errorfmt % node.abspath
345 def ignore_diskcheck_match(node, predicate, errorfmt):
348 def do_diskcheck_rcs(node, name):
350 rcs_dir = node.rcs_dir
351 except AttributeError:
352 if node.entry_exists_on_disk('RCS'):
353 rcs_dir = node.Dir('RCS')
356 node.rcs_dir = rcs_dir
358 return rcs_dir.entry_exists_on_disk(name+',v')
361 def ignore_diskcheck_rcs(node, name):
364 def do_diskcheck_sccs(node, name):
366 sccs_dir = node.sccs_dir
367 except AttributeError:
368 if node.entry_exists_on_disk('SCCS'):
369 sccs_dir = node.Dir('SCCS')
372 node.sccs_dir = sccs_dir
374 return sccs_dir.entry_exists_on_disk('s.'+name)
377 def ignore_diskcheck_sccs(node, name):
380 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
381 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
382 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
390 def set_diskcheck(list):
391 for dc in diskcheckers:
394 def diskcheck_types():
395 return map(lambda dc: dc.type, diskcheckers)
399 class EntryProxy(SCons.Util.Proxy):
400 def __get_abspath(self):
402 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
403 entry.name + "_abspath")
405 def __get_filebase(self):
406 name = self.get().name
407 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
410 def __get_suffix(self):
411 name = self.get().name
412 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
415 def __get_file(self):
416 name = self.get().name
417 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
419 def __get_base_path(self):
420 """Return the file's directory and file name, with the
423 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
424 entry.name + "_base")
426 def __get_posix_path(self):
427 """Return the path with / as the path separator,
428 regardless of platform."""
433 r = string.replace(entry.get_path(), os.sep, '/')
434 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
436 def __get_windows_path(self):
437 """Return the path with \ as the path separator,
438 regardless of platform."""
443 r = string.replace(entry.get_path(), os.sep, '\\')
444 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
446 def __get_srcnode(self):
447 return EntryProxy(self.get().srcnode())
449 def __get_srcdir(self):
450 """Returns the directory containing the source node linked to this
451 node via VariantDir(), or the directory of this node if not linked."""
452 return EntryProxy(self.get().srcnode().dir)
454 def __get_rsrcnode(self):
455 return EntryProxy(self.get().srcnode().rfile())
457 def __get_rsrcdir(self):
458 """Returns the directory containing the source node linked to this
459 node via VariantDir(), or the directory of this node if not linked."""
460 return EntryProxy(self.get().srcnode().rfile().dir)
463 return EntryProxy(self.get().dir)
465 dictSpecialAttrs = { "base" : __get_base_path,
466 "posix" : __get_posix_path,
467 "windows" : __get_windows_path,
468 "win32" : __get_windows_path,
469 "srcpath" : __get_srcnode,
470 "srcdir" : __get_srcdir,
472 "abspath" : __get_abspath,
473 "filebase" : __get_filebase,
474 "suffix" : __get_suffix,
476 "rsrcpath" : __get_rsrcnode,
477 "rsrcdir" : __get_rsrcdir,
480 def __getattr__(self, name):
481 # This is how we implement the "special" attributes
482 # such as base, posix, srcdir, etc.
484 attr_function = self.dictSpecialAttrs[name]
487 attr = SCons.Util.Proxy.__getattr__(self, name)
488 except AttributeError:
490 classname = string.split(str(entry.__class__), '.')[-1]
491 if classname[-2:] == "'>":
492 # new-style classes report their name as:
493 # "<class 'something'>"
494 # instead of the classic classes:
496 classname = classname[:-2]
497 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
500 return attr_function(self)
502 class Base(SCons.Node.Node):
503 """A generic class for file system entries. This class is for
504 when we don't know yet whether the entry being looked up is a file
505 or a directory. Instances of this class can morph into either
506 Dir or File objects by a later, more precise lookup.
508 Note: this class does not define __cmp__ and __hash__ for
509 efficiency reasons. SCons does a lot of comparing of
510 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
511 as fast as possible, which means we want to use Python's built-in
512 object identity comparisons.
515 memoizer_counters = []
517 def __init__(self, name, directory, fs):
518 """Initialize a generic Node.FS.Base object.
520 Call the superclass initialization, take care of setting up
521 our relative and absolute paths, identify our parent
522 directory, and indicate that this node should use
524 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
525 SCons.Node.Node.__init__(self)
528 self.suffix = SCons.Util.splitext(name)[1]
531 assert directory, "A directory must be provided"
533 self.abspath = directory.entry_abspath(name)
534 self.labspath = directory.entry_labspath(name)
535 if directory.path == '.':
538 self.path = directory.entry_path(name)
539 if directory.tpath == '.':
542 self.tpath = directory.entry_tpath(name)
543 self.path_elements = directory.path_elements + [self]
546 self.cwd = None # will hold the SConscript directory for target nodes
547 self.duplicate = directory.duplicate
549 def str_for_display(self):
550 return '"' + self.__str__() + '"'
552 def must_be_same(self, klass):
554 This node, which already existed, is being looked up as the
555 specified klass. Raise an exception if it isn't.
557 if self.__class__ is klass or klass is Entry:
559 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
560 (self.__class__.__name__, self.path, klass.__name__)
565 def get_suffix(self):
572 """A Node.FS.Base object's string representation is its path
576 return self._save_str()
577 return self._get_str()
579 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
583 return self._memo['_save_str']
586 result = self._get_str()
587 self._memo['_save_str'] = result
592 if self.duplicate or self.is_derived():
593 return self.get_path()
594 srcnode = self.srcnode()
595 if srcnode.stat() is None and not self.stat() is None:
596 result = self.get_path()
598 result = srcnode.get_path()
600 # We're not at the point where we're saving the string string
601 # representations of FS Nodes (because we haven't finished
602 # reading the SConscript files and need to have str() return
603 # things relative to them). That also means we can't yet
604 # cache values returned (or not returned) by stat(), since
605 # Python code in the SConscript files might still create
606 # or otherwise affect the on-disk file. So get rid of the
607 # values that the underlying stat() method saved.
608 try: del self._memo['stat']
609 except KeyError: pass
610 if not self is srcnode:
611 try: del srcnode._memo['stat']
612 except KeyError: pass
617 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
620 try: return self._memo['stat']
621 except KeyError: pass
622 try: result = self.fs.stat(self.abspath)
623 except os.error: result = None
624 self._memo['stat'] = result
628 return not self.stat() is None
631 return self.rfile().exists()
635 if st: return st[stat.ST_MTIME]
640 if st: return st[stat.ST_SIZE]
645 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
649 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
651 if hasattr(os, 'symlink'):
653 try: st = self.fs.lstat(self.abspath)
654 except os.error: return 0
655 return stat.S_ISLNK(st[stat.ST_MODE])
658 return 0 # no symlinks
660 def is_under(self, dir):
664 return self.dir.is_under(dir)
670 """If this node is in a build path, return the node
671 corresponding to its source file. Otherwise, return
674 srcdir_list = self.dir.srcdir_list()
676 srcnode = srcdir_list[0].Entry(self.name)
677 srcnode.must_be_same(self.__class__)
681 def get_path(self, dir=None):
682 """Return path relative to the current working directory of the
683 Node.FS.Base object that owns us."""
685 dir = self.fs.getcwd()
688 path_elems = self.path_elements
689 try: i = path_elems.index(dir)
690 except ValueError: pass
691 else: path_elems = path_elems[i+1:]
692 path_elems = map(lambda n: n.name, path_elems)
693 return string.join(path_elems, os.sep)
695 def set_src_builder(self, builder):
696 """Set the source code builder for this node."""
697 self.sbuilder = builder
698 if not self.has_builder():
699 self.builder_set(builder)
701 def src_builder(self):
702 """Fetch the source code builder for this node.
704 If there isn't one, we cache the source code builder specified
705 for the directory (which in turn will cache the value from its
706 parent directory, and so on up to the file system root).
710 except AttributeError:
711 scb = self.dir.src_builder()
715 def get_abspath(self):
716 """Get the absolute path of the file."""
719 def for_signature(self):
720 # Return just our name. Even an absolute path would not work,
721 # because that can change thanks to symlinks or remapped network
725 def get_subst_proxy(self):
728 except AttributeError:
729 ret = EntryProxy(self)
733 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
736 Generates a target entry that corresponds to this entry (usually
737 a source file) with the specified prefix and suffix.
739 Note that this method can be overridden dynamically for generated
740 files that need different behavior. See Tool/swig.py for
743 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
745 def _Rfindalldirs_key(self, pathlist):
748 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
750 def Rfindalldirs(self, pathlist):
752 Return all of the directories for a given path list, including
753 corresponding "backing" directories in any repositories.
755 The Node lookups are relative to this Node (typically a
756 directory), so memoizing result saves cycles from looking
757 up the same path for each target in a given directory.
760 memo_dict = self._memo['Rfindalldirs']
763 self._memo['Rfindalldirs'] = memo_dict
766 return memo_dict[pathlist]
770 create_dir_relative_to_self = self.Dir
772 for path in pathlist:
773 if isinstance(path, SCons.Node.Node):
776 dir = create_dir_relative_to_self(path)
777 result.extend(dir.get_all_rdirs())
779 memo_dict[pathlist] = result
783 def RDirs(self, pathlist):
784 """Search for a list of directories in the Repository list."""
785 cwd = self.cwd or self.fs._cwd
786 return cwd.Rfindalldirs(pathlist)
788 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
792 return self._memo['rentry']
796 if not self.exists():
797 norm_name = _my_normcase(self.name)
798 for dir in self.dir.get_all_rdirs():
800 node = dir.entries[norm_name]
802 if dir.entry_exists_on_disk(self.name):
803 result = dir.Entry(self.name)
805 self._memo['rentry'] = result
808 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
812 """This is the class for generic Node.FS entries--that is, things
813 that could be a File or a Dir, but we're just not sure yet.
814 Consequently, the methods in this class really exist just to
815 transform their associated object into the right class when the
816 time comes, and then call the same-named method in the transformed
819 def diskcheck_match(self):
822 def disambiguate(self, must_exist=None):
829 self.__class__ = File
833 # There was nothing on-disk at this location, so look in
836 # We can't just use self.srcnode() straight away because
837 # that would create an actual Node for this file in the src
838 # directory, and there might not be one. Instead, use the
839 # dir_on_disk() method to see if there's something on-disk
840 # with that name, in which case we can go ahead and call
841 # self.srcnode() to create the right type of entry.
842 srcdir = self.dir.srcnode()
843 if srcdir != self.dir and \
844 srcdir.entry_exists_on_disk(self.name) and \
845 self.srcnode().isdir():
849 msg = "No such file or directory: '%s'" % self.abspath
850 raise SCons.Errors.UserError, msg
852 self.__class__ = File
858 """We're a generic Entry, but the caller is actually looking for
859 a File at this point, so morph into one."""
860 self.__class__ = File
863 return File.rfile(self)
865 def scanner_key(self):
866 return self.get_suffix()
868 def get_contents(self):
869 """Fetch the contents of the entry.
871 Since this should return the real contents from the file
872 system, we check to see into what sort of subclass we should
875 self = self.disambiguate(must_exist=1)
876 except SCons.Errors.UserError:
877 # There was nothing on disk with which to disambiguate
878 # this entry. Leave it as an Entry, but return a null
879 # string so calls to get_contents() in emitters and the
880 # like (e.g. in qt.py) don't have to disambiguate by hand
881 # or catch the exception.
884 return self.get_contents()
886 def must_be_same(self, klass):
887 """Called to make sure a Node is a Dir. Since we're an
888 Entry, we can morph into one."""
889 if not self.__class__ is klass:
890 self.__class__ = klass
894 # The following methods can get called before the Taskmaster has
895 # had a chance to call disambiguate() directly to see if this Entry
896 # should really be a Dir or a File. We therefore use these to call
897 # disambiguate() transparently (from our caller's point of view).
899 # Right now, this minimal set of methods has been derived by just
900 # looking at some of the methods that will obviously be called early
901 # in any of the various Taskmasters' calling sequences, and then
902 # empirically figuring out which additional methods are necessary
903 # to make various tests pass.
906 """Return if the Entry exists. Check the file system to see
907 what we should turn into first. Assume a file if there's no
909 return self.disambiguate().exists()
911 def rel_path(self, other):
912 d = self.disambiguate()
913 if d.__class__ == Entry:
914 raise "rel_path() could not disambiguate File/Dir"
915 return d.rel_path(other)
918 return self.disambiguate().new_ninfo()
920 def changed_since_last_build(self, target, prev_ni):
921 return self.disambiguate().changed_since_last_build(target, prev_ni)
923 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
924 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
926 # This is for later so we can differentiate between Entry the class and Entry
927 # the method of the FS class.
933 if SCons.Memoize.use_memoizer:
934 __metaclass__ = SCons.Memoize.Memoized_Metaclass
936 # This class implements an abstraction layer for operations involving
937 # a local file system. Essentially, this wraps any function in
938 # the os, os.path or shutil modules that we use to actually go do
939 # anything with or to the local file system.
941 # Note that there's a very good chance we'll refactor this part of
942 # the architecture in some way as we really implement the interface(s)
943 # for remote file system Nodes. For example, the right architecture
944 # might be to have this be a subclass instead of a base class.
945 # Nevertheless, we're using this as a first step in that direction.
947 # We're not using chdir() yet because the calling subclass method
948 # needs to use os.chdir() directly to avoid recursion. Will we
949 # really need this one?
950 #def chdir(self, path):
951 # return os.chdir(path)
952 def chmod(self, path, mode):
953 return os.chmod(path, mode)
954 def copy(self, src, dst):
955 return shutil.copy(src, dst)
956 def copy2(self, src, dst):
957 return shutil.copy2(src, dst)
958 def exists(self, path):
959 return os.path.exists(path)
960 def getmtime(self, path):
961 return os.path.getmtime(path)
962 def getsize(self, path):
963 return os.path.getsize(path)
964 def isdir(self, path):
965 return os.path.isdir(path)
966 def isfile(self, path):
967 return os.path.isfile(path)
968 def link(self, src, dst):
969 return os.link(src, dst)
970 def lstat(self, path):
971 return os.lstat(path)
972 def listdir(self, path):
973 return os.listdir(path)
974 def makedirs(self, path):
975 return os.makedirs(path)
976 def mkdir(self, path):
977 return os.mkdir(path)
978 def rename(self, old, new):
979 return os.rename(old, new)
980 def stat(self, path):
982 def symlink(self, src, dst):
983 return os.symlink(src, dst)
984 def open(self, path):
986 def unlink(self, path):
987 return os.unlink(path)
989 if hasattr(os, 'symlink'):
990 def islink(self, path):
991 return os.path.islink(path)
993 def islink(self, path):
994 return 0 # no symlinks
996 if hasattr(os, 'readlink'):
997 def readlink(self, file):
998 return os.readlink(file)
1000 def readlink(self, file):
1005 # # Skeleton for the obvious methods we might need from the
1006 # # abstraction layer for a remote filesystem.
1007 # def upload(self, local_src, remote_dst):
1009 # def download(self, remote_src, local_dst):
1015 memoizer_counters = []
1017 def __init__(self, path = None):
1018 """Initialize the Node.FS subsystem.
1020 The supplied path is the top of the source tree, where we
1021 expect to find the top-level build file. If no path is
1022 supplied, the current directory is the default.
1024 The path argument must be a valid absolute path.
1026 if __debug__: logInstanceCreation(self, 'Node.FS')
1031 self.SConstruct_dir = None
1032 self.max_drift = default_max_drift
1036 self.pathTop = os.getcwd()
1039 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1041 self.Top = self.Dir(self.pathTop)
1043 self.Top.tpath = '.'
1044 self._cwd = self.Top
1046 DirNodeInfo.fs = self
1047 FileNodeInfo.fs = self
1049 def set_SConstruct_dir(self, dir):
1050 self.SConstruct_dir = dir
1052 def get_max_drift(self):
1053 return self.max_drift
1055 def set_max_drift(self, max_drift):
1056 self.max_drift = max_drift
1061 def chdir(self, dir, change_os_dir=0):
1062 """Change the current working directory for lookups.
1063 If change_os_dir is true, we will also change the "real" cwd
1071 os.chdir(dir.abspath)
1076 def get_root(self, drive):
1078 Returns the root directory for the specified drive, creating
1081 drive = _my_normcase(drive)
1083 return self.Root[drive]
1085 root = RootDir(drive, self)
1086 self.Root[drive] = root
1088 self.Root[self.defaultDrive] = root
1089 elif drive == self.defaultDrive:
1090 self.Root[''] = root
1093 def _lookup(self, p, directory, fsclass, create=1):
1095 The generic entry point for Node lookup with user-supplied data.
1097 This translates arbitrary input into a canonical Node.FS object
1098 of the specified fsclass. The general approach for strings is
1099 to turn it into a fully normalized absolute path and then call
1100 the root directory's lookup_abs() method for the heavy lifting.
1102 If the path name begins with '#', it is unconditionally
1103 interpreted relative to the top-level directory of this FS. '#'
1104 is treated as a synonym for the top-level SConstruct directory,
1105 much like '~' is treated as a synonym for the user's home
1106 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1107 to the 'foo' subdirectory underneath the top-level SConstruct
1110 If the path name is relative, then the path is looked up relative
1111 to the specified directory, or the current directory (self._cwd,
1112 typically the SConscript directory) if the specified directory
1115 if isinstance(p, Base):
1116 # It's already a Node.FS object. Make sure it's the right
1118 p.must_be_same(fsclass)
1120 # str(p) in case it's something like a proxy object
1123 initial_hash = (p[0:1] == '#')
1125 # There was an initial '#', so we strip it and override
1126 # whatever directory they may have specified with the
1127 # top-level SConstruct directory.
1129 directory = self.Top
1131 if directory and not isinstance(directory, Dir):
1132 directory = self.Dir(directory)
1135 drive, p = os.path.splitdrive(p)
1139 # This causes a naked drive letter to be treated as a synonym
1140 # for the root directory on that drive.
1142 absolute = os.path.isabs(p)
1144 needs_normpath = needs_normpath_check.match(p)
1146 if initial_hash or not absolute:
1147 # This is a relative lookup, either to the top-level
1148 # SConstruct directory (because of the initial '#') or to
1149 # the current directory (the path name is not absolute).
1150 # Add the string to the appropriate directory lookup path,
1151 # after which the whole thing gets normalized.
1153 directory = self._cwd
1155 p = directory.labspath + '/' + p
1157 p = directory.labspath
1160 p = os.path.normpath(p)
1162 if drive or absolute:
1163 root = self.get_root(drive)
1166 directory = self._cwd
1167 root = directory.root
1170 p = string.replace(p, os.sep, '/')
1171 return root._lookup_abs(p, fsclass, create)
1173 def Entry(self, name, directory = None, create = 1):
1174 """Lookup or create a generic Entry node with the specified name.
1175 If the name is a relative path (begins with ./, ../, or a file
1176 name), then it is looked up relative to the supplied directory
1177 node, or to the top level directory of the FS (supplied at
1178 construction time) if no directory is supplied.
1180 return self._lookup(name, directory, Entry, create)
1182 def File(self, name, directory = None, create = 1):
1183 """Lookup or create a File node with the specified name. If
1184 the name is a relative path (begins with ./, ../, or a file name),
1185 then it is looked up relative to the supplied directory node,
1186 or to the top level directory of the FS (supplied at construction
1187 time) if no directory is supplied.
1189 This method will raise TypeError if a directory is found at the
1192 return self._lookup(name, directory, File, create)
1194 def Dir(self, name, directory = None, create = True):
1195 """Lookup or create a Dir node with the specified name. If
1196 the name is a relative path (begins with ./, ../, or a file name),
1197 then it is looked up relative to the supplied directory node,
1198 or to the top level directory of the FS (supplied at construction
1199 time) if no directory is supplied.
1201 This method will raise TypeError if a normal file is found at the
1204 return self._lookup(name, directory, Dir, create)
1206 def VariantDir(self, variant_dir, src_dir, duplicate=1):
1207 """Link the supplied variant directory to the source directory
1208 for purposes of building files."""
1210 if not isinstance(src_dir, SCons.Node.Node):
1211 src_dir = self.Dir(src_dir)
1212 if not isinstance(variant_dir, SCons.Node.Node):
1213 variant_dir = self.Dir(variant_dir)
1214 if src_dir.is_under(variant_dir):
1215 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1216 if variant_dir.srcdir:
1217 if variant_dir.srcdir == src_dir:
1218 return # We already did this.
1219 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1220 variant_dir.link(src_dir, duplicate)
1222 def Repository(self, *dirs):
1223 """Specify Repository directories to search."""
1225 if not isinstance(d, SCons.Node.Node):
1227 self.Top.addRepository(d)
1229 def variant_dir_target_climb(self, orig, dir, tail):
1230 """Create targets in corresponding variant directories
1232 Climb the directory tree, and look up path names
1233 relative to any linked variant directories we find.
1235 Even though this loops and walks up the tree, we don't memoize
1236 the return value because this is really only used to process
1237 the command-line targets.
1241 fmt = "building associated VariantDir targets: %s"
1244 for bd in dir.variant_dirs:
1245 if start_dir.is_under(bd):
1246 # If already in the build-dir location, don't reflect
1247 return [orig], fmt % str(orig)
1248 p = apply(os.path.join, [bd.path] + tail)
1249 targets.append(self.Entry(p))
1250 tail = [dir.name] + tail
1253 message = fmt % string.join(map(str, targets))
1254 return targets, message
1256 def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1260 This is mainly a shim layer
1264 return cwd.glob(pathname, ondisk, source, strings)
1266 class DirNodeInfo(SCons.Node.NodeInfoBase):
1267 # This should get reset by the FS initialization.
1268 current_version_id = 1
1272 def str_to_node(self, s):
1276 drive, s = os.path.splitdrive(s)
1278 root = self.fs.get_root(drive)
1279 if not os.path.isabs(s):
1280 s = top.labspath + '/' + s
1281 return root._lookup_abs(s, Entry)
1283 class DirBuildInfo(SCons.Node.BuildInfoBase):
1284 current_version_id = 1
1286 glob_magic_check = re.compile('[*?[]')
1288 def has_glob_magic(s):
1289 return glob_magic_check.search(s) is not None
1292 """A class for directories in a file system.
1295 memoizer_counters = []
1297 NodeInfo = DirNodeInfo
1298 BuildInfo = DirBuildInfo
1300 def __init__(self, name, directory, fs):
1301 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1302 Base.__init__(self, name, directory, fs)
1306 """Turn a file system Node (either a freshly initialized directory
1307 object or a separate Entry object) into a proper directory object.
1309 Set up this directory's entries and hook it into the file
1310 system tree. Specify that directories (this Node) don't use
1311 signatures for calculating whether they're current.
1314 self.repositories = []
1318 self.entries['.'] = self
1319 self.entries['..'] = self.dir
1322 self._sconsign = None
1323 self.variant_dirs = []
1324 self.root = self.dir.root
1326 # Don't just reset the executor, replace its action list,
1327 # because it might have some pre-or post-actions that need to
1329 self.builder = get_MkdirBuilder()
1330 self.get_executor().set_action_list(self.builder.action)
1332 def diskcheck_match(self):
1333 diskcheck_match(self, self.isfile,
1334 "File %s found where directory expected.")
1336 def __clearRepositoryCache(self, duplicate=None):
1337 """Called when we change the repository(ies) for a directory.
1338 This clears any cached information that is invalidated by changing
1341 for node in self.entries.values():
1342 if node != self.dir:
1343 if node != self and isinstance(node, Dir):
1344 node.__clearRepositoryCache(duplicate)
1349 except AttributeError:
1351 if duplicate != None:
1352 node.duplicate=duplicate
1354 def __resetDuplicate(self, node):
1356 node.duplicate = node.get_dir().duplicate
1358 def Entry(self, name):
1360 Looks up or creates an entry node named 'name' relative to
1363 return self.fs.Entry(name, self)
1365 def Dir(self, name, create=True):
1367 Looks up or creates a directory node named 'name' relative to
1370 dir = self.fs.Dir(name, self, create)
1373 def File(self, name):
1375 Looks up or creates a file node named 'name' relative to
1378 return self.fs.File(name, self)
1380 def _lookup_rel(self, name, klass, create=1):
1382 Looks up a *normalized* relative path name, relative to this
1385 This method is intended for use by internal lookups with
1386 already-normalized path data. For general-purpose lookups,
1387 use the Entry(), Dir() and File() methods above.
1389 This method does *no* input checking and will die or give
1390 incorrect results if it's passed a non-normalized path name (e.g.,
1391 a path containing '..'), an absolute path name, a top-relative
1392 ('#foo') path name, or any kind of object.
1394 name = self.entry_labspath(name)
1395 return self.root._lookup_abs(name, klass, create)
1397 def link(self, srcdir, duplicate):
1398 """Set this directory as the variant directory for the
1399 supplied source directory."""
1400 self.srcdir = srcdir
1401 self.duplicate = duplicate
1402 self.__clearRepositoryCache(duplicate)
1403 srcdir.variant_dirs.append(self)
1405 def getRepositories(self):
1406 """Returns a list of repositories for this directory.
1408 if self.srcdir and not self.duplicate:
1409 return self.srcdir.get_all_rdirs() + self.repositories
1410 return self.repositories
1412 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1414 def get_all_rdirs(self):
1416 return list(self._memo['get_all_rdirs'])
1424 for rep in dir.getRepositories():
1425 result.append(rep.Dir(fname))
1429 fname = dir.name + os.sep + fname
1432 self._memo['get_all_rdirs'] = list(result)
1436 def addRepository(self, dir):
1437 if dir != self and not dir in self.repositories:
1438 self.repositories.append(dir)
1440 self.__clearRepositoryCache()
1443 return self.entries['..']
1445 def _rel_path_key(self, other):
1448 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1450 def rel_path(self, other):
1451 """Return a path to "other" relative to this directory.
1454 # This complicated and expensive method, which constructs relative
1455 # paths between arbitrary Node.FS objects, is no longer used
1456 # by SCons itself. It was introduced to store dependency paths
1457 # in .sconsign files relative to the target, but that ended up
1458 # being significantly inefficient.
1460 # We're continuing to support the method because some SConstruct
1461 # files out there started using it when it was available, and
1462 # we're all about backwards compatibility..
1465 memo_dict = self._memo['rel_path']
1468 self._memo['rel_path'] = memo_dict
1471 return memo_dict[other]
1479 elif not other in self.path_elements:
1482 other_dir = other.get_dir()
1483 except AttributeError:
1486 if other_dir is None:
1489 dir_rel_path = self.rel_path(other_dir)
1490 if dir_rel_path == '.':
1493 result = dir_rel_path + os.sep + other.name
1497 i = self.path_elements.index(other) + 1
1499 path_elems = ['..'] * (len(self.path_elements) - i) \
1500 + map(lambda n: n.name, other.path_elements[i:])
1502 result = string.join(path_elems, os.sep)
1504 memo_dict[other] = result
1508 def get_env_scanner(self, env, kw={}):
1509 import SCons.Defaults
1510 return SCons.Defaults.DirEntryScanner
1512 def get_target_scanner(self):
1513 import SCons.Defaults
1514 return SCons.Defaults.DirEntryScanner
1516 def get_found_includes(self, env, scanner, path):
1517 """Return this directory's implicit dependencies.
1519 We don't bother caching the results because the scan typically
1520 shouldn't be requested more than once (as opposed to scanning
1521 .h file contents, which can be requested as many times as the
1522 files is #included by other files).
1526 # Clear cached info for this Dir. If we already visited this
1527 # directory on our walk down the tree (because we didn't know at
1528 # that point it was being used as the source for another Node)
1529 # then we may have calculated build signature before realizing
1530 # we had to scan the disk. Now that we have to, though, we need
1531 # to invalidate the old calculated signature so that any node
1532 # dependent on our directory structure gets one that includes
1533 # info about everything on disk.
1535 return scanner(self, env, path)
1538 # Taskmaster interface subsystem
1544 def build(self, **kw):
1545 """A null "builder" for directories."""
1547 if not self.builder is MkdirBuilder:
1548 apply(SCons.Node.Node.build, [self,], kw)
1555 """Create this directory, silently and without worrying about
1556 whether the builder is the default or not."""
1562 listDirs.append(parent)
1565 raise SCons.Errors.StopError, parent.path
1568 for dirnode in listDirs:
1570 # Don't call dirnode.build(), call the base Node method
1571 # directly because we definitely *must* create this
1572 # directory. The dirnode.build() method will suppress
1573 # the build if it's the default builder.
1574 SCons.Node.Node.build(dirnode)
1575 dirnode.get_executor().nullify()
1576 # The build() action may or may not have actually
1577 # created the directory, depending on whether the -n
1578 # option was used or not. Delete the _exists and
1579 # _rexists attributes so they can be reevaluated.
1584 def multiple_side_effect_has_builder(self):
1586 return not self.builder is MkdirBuilder and self.has_builder()
1588 def alter_targets(self):
1589 """Return any corresponding targets in a variant directory.
1591 return self.fs.variant_dir_target_climb(self, self, [])
1593 def scanner_key(self):
1594 """A directory does not get scanned."""
1597 def get_contents(self):
1598 """Return content signatures and names of all our children
1599 separated by new-lines. Ensure that the nodes are sorted."""
1601 name_cmp = lambda a, b: cmp(a.name, b.name)
1602 sorted_children = self.children()[:]
1603 sorted_children.sort(name_cmp)
1604 for node in sorted_children:
1605 contents.append('%s %s\n' % (node.get_csig(), node.name))
1606 return string.join(contents, '')
1609 """Compute the content signature for Directory nodes. In
1610 general, this is not needed and the content signature is not
1611 stored in the DirNodeInfo. However, if get_contents on a Dir
1612 node is called which has a child directory, the child
1613 directory should return the hash of its contents."""
1614 contents = self.get_contents()
1615 return SCons.Util.MD5signature(contents)
1617 def do_duplicate(self, src):
1620 changed_since_last_build = SCons.Node.Node.state_has_changed
1622 def is_up_to_date(self):
1623 """If any child is not up-to-date, then this directory isn't,
1625 if not self.builder is MkdirBuilder and not self.exists():
1627 up_to_date = SCons.Node.up_to_date
1628 for kid in self.children():
1629 if kid.get_state() > up_to_date:
1634 if not self.exists():
1635 norm_name = _my_normcase(self.name)
1636 for dir in self.dir.get_all_rdirs():
1637 try: node = dir.entries[norm_name]
1638 except KeyError: node = dir.dir_on_disk(self.name)
1639 if node and node.exists() and \
1640 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1645 """Return the .sconsign file info for this directory,
1646 creating it first if necessary."""
1647 if not self._sconsign:
1648 import SCons.SConsign
1649 self._sconsign = SCons.SConsign.ForDirectory(self)
1650 return self._sconsign
1653 """Dir has a special need for srcnode()...if we
1654 have a srcdir attribute set, then that *is* our srcnode."""
1657 return Base.srcnode(self)
1659 def get_timestamp(self):
1660 """Return the latest timestamp from among our children"""
1662 for kid in self.children():
1663 if kid.get_timestamp() > stamp:
1664 stamp = kid.get_timestamp()
1667 def entry_abspath(self, name):
1668 return self.abspath + os.sep + name
1670 def entry_labspath(self, name):
1671 return self.labspath + '/' + name
1673 def entry_path(self, name):
1674 return self.path + os.sep + name
1676 def entry_tpath(self, name):
1677 return self.tpath + os.sep + name
1679 def entry_exists_on_disk(self, name):
1681 d = self.on_disk_entries
1682 except AttributeError:
1685 entries = os.listdir(self.abspath)
1689 for entry in map(_my_normcase, entries):
1691 self.on_disk_entries = d
1692 return d.has_key(_my_normcase(name))
1694 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1696 def srcdir_list(self):
1698 return self._memo['srcdir_list']
1708 result.append(dir.srcdir.Dir(dirname))
1709 dirname = dir.name + os.sep + dirname
1712 self._memo['srcdir_list'] = result
1716 def srcdir_duplicate(self, name):
1717 for dir in self.srcdir_list():
1718 if self.is_under(dir):
1719 # We shouldn't source from something in the build path;
1720 # variant_dir is probably under src_dir, in which case
1721 # we are reflecting.
1723 if dir.entry_exists_on_disk(name):
1724 srcnode = dir.Entry(name).disambiguate()
1726 node = self.Entry(name).disambiguate()
1727 node.do_duplicate(srcnode)
1733 def _srcdir_find_file_key(self, filename):
1736 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1738 def srcdir_find_file(self, filename):
1740 memo_dict = self._memo['srcdir_find_file']
1743 self._memo['srcdir_find_file'] = memo_dict
1746 return memo_dict[filename]
1751 if (isinstance(node, File) or isinstance(node, Entry)) and \
1752 (node.is_derived() or node.exists()):
1756 norm_name = _my_normcase(filename)
1758 for rdir in self.get_all_rdirs():
1759 try: node = rdir.entries[norm_name]
1760 except KeyError: node = rdir.file_on_disk(filename)
1761 else: node = func(node)
1763 result = (node, self)
1764 memo_dict[filename] = result
1767 for srcdir in self.srcdir_list():
1768 for rdir in srcdir.get_all_rdirs():
1769 try: node = rdir.entries[norm_name]
1770 except KeyError: node = rdir.file_on_disk(filename)
1771 else: node = func(node)
1773 result = (File(filename, self, self.fs), srcdir)
1774 memo_dict[filename] = result
1777 result = (None, None)
1778 memo_dict[filename] = result
1781 def dir_on_disk(self, name):
1782 if self.entry_exists_on_disk(name):
1783 try: return self.Dir(name)
1784 except TypeError: pass
1785 node = self.srcdir_duplicate(name)
1786 if isinstance(node, File):
1790 def file_on_disk(self, name):
1791 if self.entry_exists_on_disk(name) or \
1792 diskcheck_rcs(self, name) or \
1793 diskcheck_sccs(self, name):
1794 try: return self.File(name)
1795 except TypeError: pass
1796 node = self.srcdir_duplicate(name)
1797 if isinstance(node, Dir):
1801 def walk(self, func, arg):
1803 Walk this directory tree by calling the specified function
1804 for each directory in the tree.
1806 This behaves like the os.path.walk() function, but for in-memory
1807 Node.FS.Dir objects. The function takes the same arguments as
1808 the functions passed to os.path.walk():
1810 func(arg, dirname, fnames)
1812 Except that "dirname" will actually be the directory *Node*,
1813 not the string. The '.' and '..' entries are excluded from
1814 fnames. The fnames list may be modified in-place to filter the
1815 subdirectories visited or otherwise impose a specific order.
1816 The "arg" argument is always passed to func() and may be used
1817 in any way (or ignored, passing None is common).
1819 entries = self.entries
1820 names = entries.keys()
1823 func(arg, self, names)
1824 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1825 for dirname in filter(select_dirs, names):
1826 entries[dirname].walk(func, arg)
1828 def glob(self, pathname, ondisk=True, source=False, strings=False):
1830 Returns a list of Nodes (or strings) matching a specified
1833 Pathname patterns follow UNIX shell semantics: * matches
1834 any-length strings of any characters, ? matches any character,
1835 and [] can enclose lists or ranges of characters. Matches do
1836 not span directory separators.
1838 The matches take into account Repositories, returning local
1839 Nodes if a corresponding entry exists in a Repository (either
1840 an in-memory Node or something on disk).
1842 By defafult, the glob() function matches entries that exist
1843 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1844 argument to False (or some other non-true value) causes the glob()
1845 function to only match in-memory Nodes. The default behavior is
1846 to return both the on-disk and in-memory Nodes.
1848 The "source" argument, when true, specifies that corresponding
1849 source Nodes must be returned if you're globbing in a build
1850 directory (initialized with VariantDir()). The default behavior
1851 is to return Nodes local to the VariantDir().
1853 The "strings" argument, when true, returns the matches as strings,
1854 not Nodes. The strings are path names relative to this directory.
1856 The underlying algorithm is adapted from the glob.glob() function
1857 in the Python library (but heavily modified), and uses fnmatch()
1860 dirname, basename = os.path.split(pathname)
1862 return self._glob1(basename, ondisk, source, strings)
1863 if has_glob_magic(dirname):
1864 list = self.glob(dirname, ondisk, source, strings=False)
1866 list = [self.Dir(dirname, create=True)]
1869 r = dir._glob1(basename, ondisk, source, strings)
1871 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1873 result.sort(lambda a, b: cmp(str(a), str(b)))
1876 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1878 Globs for and returns a list of entry names matching a single
1879 pattern in this directory.
1881 This searches any repositories and source directories for
1882 corresponding entries and returns a Node (or string) relative
1883 to the current directory if an entry is found anywhere.
1885 TODO: handle pattern with no wildcard
1887 search_dir_list = self.get_all_rdirs()
1888 for srcdir in self.srcdir_list():
1889 search_dir_list.extend(srcdir.get_all_rdirs())
1892 for dir in search_dir_list:
1893 # We use the .name attribute from the Node because the keys of
1894 # the dir.entries dictionary are normalized (that is, all upper
1895 # case) on case-insensitive systems like Windows.
1896 #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
1897 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1898 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1899 names.extend(node_names)
1902 disk_names = os.listdir(dir.abspath)
1906 names.extend(disk_names)
1908 # We're going to return corresponding Nodes in
1909 # the local directory, so we need to make sure
1910 # those Nodes exist. We only want to create
1911 # Nodes for the entries that will match the
1912 # specified pattern, though, which means we
1913 # need to filter the list here, even though
1914 # the overall list will also be filtered later,
1915 # after we exit this loop.
1916 if pattern[0] != '.':
1917 #disk_names = [ d for d in disk_names if d[0] != '.' ]
1918 disk_names = filter(lambda x: x[0] != '.', disk_names)
1919 disk_names = fnmatch.filter(disk_names, pattern)
1920 rep_nodes = map(dir.Entry, disk_names)
1921 #rep_nodes = [ n.disambiguate() for n in rep_nodes ]
1922 rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
1923 for node, name in izip(rep_nodes, disk_names):
1924 n = self.Entry(name)
1925 if n.__class__ != node.__class__:
1926 n.__class__ = node.__class__
1930 if pattern[0] != '.':
1931 #names = [ n for n in names if n[0] != '.' ]
1932 names = filter(lambda x: x[0] != '.', names)
1933 names = fnmatch.filter(names, pattern)
1938 #return [ self.entries[_my_normcase(n)] for n in names ]
1939 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1942 """A class for the root directory of a file system.
1944 This is the same as a Dir class, except that the path separator
1945 ('/' or '\\') is actually part of the name, so we don't need to
1946 add a separator when creating the path names of entries within
1949 def __init__(self, name, fs):
1950 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1951 # We're going to be our own parent directory (".." entry and .dir
1952 # attribute) so we have to set up some values so Base.__init__()
1953 # won't gag won't it calls some of our methods.
1958 self.path_elements = []
1961 Base.__init__(self, name, self, fs)
1963 # Now set our paths to what we really want them to be: the
1964 # initial drive letter (the name) plus the directory separator,
1965 # except for the "lookup abspath," which does not have the
1967 self.abspath = name + os.sep
1969 self.path = name + os.sep
1970 self.tpath = name + os.sep
1973 self._lookupDict = {}
1975 # The // and os.sep + os.sep entries are necessary because
1976 # os.path.normpath() seems to preserve double slashes at the
1977 # beginning of a path (presumably for UNC path names), but
1978 # collapses triple slashes to a single slash.
1979 self._lookupDict[''] = self
1980 self._lookupDict['/'] = self
1981 self._lookupDict['//'] = self
1982 self._lookupDict[os.sep] = self
1983 self._lookupDict[os.sep + os.sep] = self
1985 def must_be_same(self, klass):
1988 Base.must_be_same(self, klass)
1990 def _lookup_abs(self, p, klass, create=1):
1992 Fast (?) lookup of a *normalized* absolute path.
1994 This method is intended for use by internal lookups with
1995 already-normalized path data. For general-purpose lookups,
1996 use the FS.Entry(), FS.Dir() or FS.File() methods.
1998 The caller is responsible for making sure we're passed a
1999 normalized absolute path; we merely let Python's dictionary look
2000 up and return the One True Node.FS object for the path.
2002 If no Node for the specified "p" doesn't already exist, and
2003 "create" is specified, the Node may be created after recursive
2004 invocation to find or create the parent directory or directories.
2008 result = self._lookupDict[k]
2011 raise SCons.Errors.UserError
2012 # There is no Node for this path name, and we're allowed
2014 dir_name, file_name = os.path.split(p)
2015 dir_node = self._lookup_abs(dir_name, Dir)
2016 result = klass(file_name, dir_node, self.fs)
2018 # Double-check on disk (as configured) that the Node we
2019 # created matches whatever is out there in the real world.
2020 result.diskcheck_match()
2022 self._lookupDict[k] = result
2023 dir_node.entries[_my_normcase(file_name)] = result
2024 dir_node.implicit = None
2026 # There is already a Node for this path name. Allow it to
2027 # complain if we were looking for an inappropriate type.
2028 result.must_be_same(klass)
2034 def entry_abspath(self, name):
2035 return self.abspath + name
2037 def entry_labspath(self, name):
2040 def entry_path(self, name):
2041 return self.path + name
2043 def entry_tpath(self, name):
2044 return self.tpath + name
2046 def is_under(self, dir):
2058 def src_builder(self):
2061 class FileNodeInfo(SCons.Node.NodeInfoBase):
2062 current_version_id = 1
2064 field_list = ['csig', 'timestamp', 'size']
2066 # This should get reset by the FS initialization.
2069 def str_to_node(self, s):
2073 drive, s = os.path.splitdrive(s)
2075 root = self.fs.get_root(drive)
2076 if not os.path.isabs(s):
2077 s = top.labspath + '/' + s
2078 return root._lookup_abs(s, Entry)
2080 class FileBuildInfo(SCons.Node.BuildInfoBase):
2081 current_version_id = 1
2083 def convert_to_sconsign(self):
2085 Converts this FileBuildInfo object for writing to a .sconsign file
2087 This replaces each Node in our various dependency lists with its
2088 usual string representation: relative to the top-level SConstruct
2089 directory, or an absolute path if it's outside.
2097 except AttributeError:
2100 s = string.replace(s, os.sep, '/')
2102 for attr in ['bsources', 'bdepends', 'bimplicit']:
2104 val = getattr(self, attr)
2105 except AttributeError:
2108 setattr(self, attr, map(node_to_str, val))
2109 def convert_from_sconsign(self, dir, name):
2111 Converts a newly-read FileBuildInfo object for in-SCons use
2113 For normal up-to-date checking, we don't have any conversion to
2114 perform--but we're leaving this method here to make that clear.
2117 def prepare_dependencies(self):
2119 Prepares a FileBuildInfo object for explaining what changed
2121 The bsources, bdepends and bimplicit lists have all been
2122 stored on disk as paths relative to the top-level SConstruct
2123 directory. Convert the strings to actual Nodes (for use by the
2124 --debug=explain code and --implicit-cache).
2127 ('bsources', 'bsourcesigs'),
2128 ('bdepends', 'bdependsigs'),
2129 ('bimplicit', 'bimplicitsigs'),
2131 for (nattr, sattr) in attrs:
2133 strings = getattr(self, nattr)
2134 nodeinfos = getattr(self, sattr)
2135 except AttributeError:
2139 for s, ni in izip(strings, nodeinfos):
2140 if not isinstance(s, SCons.Node.Node):
2141 s = ni.str_to_node(s)
2143 setattr(self, nattr, nodes)
2144 def format(self, names=0):
2146 bkids = self.bsources + self.bdepends + self.bimplicit
2147 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
2148 for bkid, bkidsig in izip(bkids, bkidsigs):
2149 result.append(str(bkid) + ': ' +
2150 string.join(bkidsig.format(names=names), ' '))
2151 result.append('%s [%s]' % (self.bactsig, self.bact))
2152 return string.join(result, '\n')
2155 """A class for files in a file system.
2158 memoizer_counters = []
2160 NodeInfo = FileNodeInfo
2161 BuildInfo = FileBuildInfo
2163 def diskcheck_match(self):
2164 diskcheck_match(self, self.isdir,
2165 "Directory %s found where file expected.")
2167 def __init__(self, name, directory, fs):
2168 if __debug__: logInstanceCreation(self, 'Node.FS.File')
2169 Base.__init__(self, name, directory, fs)
2172 def Entry(self, name):
2173 """Create an entry node named 'name' relative to
2174 the SConscript directory of this file."""
2175 cwd = self.cwd or self.fs._cwd
2176 return cwd.Entry(name)
2178 def Dir(self, name, create=True):
2179 """Create a directory node named 'name' relative to
2180 the SConscript directory of this file."""
2181 cwd = self.cwd or self.fs._cwd
2182 return cwd.Dir(name, create)
2184 def Dirs(self, pathlist):
2185 """Create a list of directories relative to the SConscript
2186 directory of this file."""
2187 return map(lambda p, s=self: s.Dir(p), pathlist)
2189 def File(self, name):
2190 """Create a file node named 'name' relative to
2191 the SConscript directory of this file."""
2192 cwd = self.cwd or self.fs._cwd
2193 return cwd.File(name)
2195 #def generate_build_dict(self):
2196 # """Return an appropriate dictionary of values for building
2198 # return {'Dir' : self.Dir,
2199 # 'File' : self.File,
2200 # 'RDirs' : self.RDirs}
2203 """Turn a file system node into a File object."""
2204 self.scanner_paths = {}
2205 if not hasattr(self, '_local'):
2208 # If there was already a Builder set on this entry, then
2209 # we need to make sure we call the target-decider function,
2210 # not the source-decider. Reaching in and doing this by hand
2211 # is a little bogus. We'd prefer to handle this by adding
2212 # an Entry.builder_set() method that disambiguates like the
2213 # other methods, but that starts running into problems with the
2214 # fragile way we initialize Dir Nodes with their Mkdir builders,
2215 # yet still allow them to be overridden by the user. Since it's
2216 # not clear right now how to fix that, stick with what works
2217 # until it becomes clear...
2218 if self.has_builder():
2219 self.changed_since_last_build = self.decide_target
2221 def scanner_key(self):
2222 return self.get_suffix()
2224 def get_contents(self):
2225 if not self.rexists():
2227 fname = self.rfile().abspath
2229 r = open(fname, "rb").read()
2230 except EnvironmentError, e:
2236 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2240 return self._memo['get_size']
2245 size = self.rfile().getsize()
2249 self._memo['get_size'] = size
2253 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2255 def get_timestamp(self):
2257 return self._memo['get_timestamp']
2262 timestamp = self.rfile().getmtime()
2266 self._memo['get_timestamp'] = timestamp
2270 def store_info(self):
2271 # Merge our build information into the already-stored entry.
2272 # This accomodates "chained builds" where a file that's a target
2273 # in one build (SConstruct file) is a source in a different build.
2274 # See test/chained-build.py for the use case.
2276 self.dir.sconsign().store_info(self.name, self)
2278 convert_copy_attrs = [
2288 convert_sig_attrs = [
2294 def convert_old_entry(self, old_entry):
2295 # Convert a .sconsign entry from before the Big Signature
2296 # Refactoring, doing what we can to convert its information
2297 # to the new .sconsign entry format.
2299 # The old format looked essentially like this:
2308 # .bsourcesigs ("signature" list)
2310 # .bdependsigs ("signature" list)
2312 # .bimplicitsigs ("signature" list)
2316 # The new format looks like this:
2323 # .binfo (BuildInfo)
2325 # .bsourcesigs (NodeInfo list)
2331 # .bdependsigs (NodeInfo list)
2337 # .bimplicitsigs (NodeInfo list)
2345 # The basic idea of the new structure is that a NodeInfo always
2346 # holds all available information about the state of a given Node
2347 # at a certain point in time. The various .b*sigs lists can just
2348 # be a list of pointers to the .ninfo attributes of the different
2349 # dependent nodes, without any copying of information until it's
2350 # time to pickle it for writing out to a .sconsign file.
2352 # The complicating issue is that the *old* format only stored one
2353 # "signature" per dependency, based on however the *last* build
2354 # was configured. We don't know from just looking at it whether
2355 # it was a build signature, a content signature, or a timestamp
2356 # "signature". Since we no longer use build signatures, the
2357 # best we can do is look at the length and if it's thirty two,
2358 # assume that it was (or might have been) a content signature.
2359 # If it was actually a build signature, then it will cause a
2360 # rebuild anyway when it doesn't match the new content signature,
2361 # but that's probably the best we can do.
2362 import SCons.SConsign
2363 new_entry = SCons.SConsign.SConsignEntry()
2364 new_entry.binfo = self.new_binfo()
2365 binfo = new_entry.binfo
2366 for attr in self.convert_copy_attrs:
2368 value = getattr(old_entry, attr)
2369 except AttributeError:
2372 setattr(binfo, attr, value)
2373 delattr(old_entry, attr)
2374 for attr in self.convert_sig_attrs:
2376 sig_list = getattr(old_entry, attr)
2377 except AttributeError:
2381 for sig in sig_list:
2382 ninfo = self.new_ninfo()
2386 ninfo.timestamp = sig
2388 setattr(binfo, attr, value)
2389 delattr(old_entry, attr)
2392 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2394 def get_stored_info(self):
2396 return self._memo['get_stored_info']
2401 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2402 except (KeyError, EnvironmentError):
2403 import SCons.SConsign
2404 sconsign_entry = SCons.SConsign.SConsignEntry()
2405 sconsign_entry.binfo = self.new_binfo()
2406 sconsign_entry.ninfo = self.new_ninfo()
2408 if isinstance(sconsign_entry, FileBuildInfo):
2409 # This is a .sconsign file from before the Big Signature
2410 # Refactoring; convert it as best we can.
2411 sconsign_entry = self.convert_old_entry(sconsign_entry)
2413 delattr(sconsign_entry.ninfo, 'bsig')
2414 except AttributeError:
2417 self._memo['get_stored_info'] = sconsign_entry
2419 return sconsign_entry
2421 def get_stored_implicit(self):
2422 binfo = self.get_stored_info().binfo
2423 binfo.prepare_dependencies()
2424 try: return binfo.bimplicit
2425 except AttributeError: return None
2427 def rel_path(self, other):
2428 return self.dir.rel_path(other)
2430 def _get_found_includes_key(self, env, scanner, path):
2431 return (id(env), id(scanner), path)
2433 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2435 def get_found_includes(self, env, scanner, path):
2436 """Return the included implicit dependencies in this file.
2437 Cache results so we only scan the file once per path
2438 regardless of how many times this information is requested.
2440 memo_key = (id(env), id(scanner), path)
2442 memo_dict = self._memo['get_found_includes']
2445 self._memo['get_found_includes'] = memo_dict
2448 return memo_dict[memo_key]
2453 result = scanner(self, env, path)
2454 result = map(lambda N: N.disambiguate(), result)
2458 memo_dict[memo_key] = result
2462 def _createDir(self):
2463 # ensure that the directories for this node are
2467 def retrieve_from_cache(self):
2468 """Try to retrieve the node's content from a cache
2470 This method is called from multiple threads in a parallel build,
2471 so only do thread safe stuff here. Do thread unsafe stuff in
2474 Returns true iff the node was successfully retrieved.
2478 if not self.is_derived():
2480 return self.get_build_env().get_CacheDir().retrieve(self)
2484 Called just after this node is successfully built.
2486 # Push this file out to cache before the superclass Node.built()
2487 # method has a chance to clear the build signature, which it
2488 # will do if this file has a source scanner.
2490 # We have to clear the memoized values *before* we push it to
2491 # cache so that the memoization of the self.exists() return
2492 # value doesn't interfere.
2493 self.clear_memoized_values()
2495 self.get_build_env().get_CacheDir().push(self)
2496 SCons.Node.Node.built(self)
2500 self.get_build_env().get_CacheDir().push_if_forced(self)
2502 ninfo = self.get_ninfo()
2504 csig = self.get_max_drift_csig()
2508 ninfo.timestamp = self.get_timestamp()
2509 ninfo.size = self.get_size()
2511 if not self.has_builder():
2512 # This is a source file, but it might have been a target file
2513 # in another build that included more of the DAG. Copy
2514 # any build information that's stored in the .sconsign file
2515 # into our binfo object so it doesn't get lost.
2516 old = self.get_stored_info()
2517 self.get_binfo().__dict__.update(old.binfo.__dict__)
2521 def find_src_builder(self):
2524 scb = self.dir.src_builder()
2526 if diskcheck_sccs(self.dir, self.name):
2527 scb = get_DefaultSCCSBuilder()
2528 elif diskcheck_rcs(self.dir, self.name):
2529 scb = get_DefaultRCSBuilder()
2535 except AttributeError:
2538 self.builder_set(scb)
2541 def has_src_builder(self):
2542 """Return whether this Node has a source builder or not.
2544 If this Node doesn't have an explicit source code builder, this
2545 is where we figure out, on the fly, if there's a transparent
2546 source code builder for it.
2548 Note that if we found a source builder, we also set the
2549 self.builder attribute, so that all of the methods that actually
2550 *build* this file don't have to do anything different.
2554 except AttributeError:
2555 scb = self.sbuilder = self.find_src_builder()
2556 return not scb is None
2558 def alter_targets(self):
2559 """Return any corresponding targets in a variant directory.
2561 if self.is_derived():
2563 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2565 def _rmv_existing(self):
2566 self.clear_memoized_values()
2567 e = Unlink(self, [], None)
2568 if isinstance(e, SCons.Errors.BuildError):
2572 # Taskmaster interface subsystem
2575 def make_ready(self):
2576 self.has_src_builder()
2580 """Prepare for this file to be created."""
2581 SCons.Node.Node.prepare(self)
2583 if self.get_state() != SCons.Node.up_to_date:
2585 if self.is_derived() and not self.precious:
2586 self._rmv_existing()
2590 except SCons.Errors.StopError, drive:
2591 desc = "No drive `%s' for target `%s'." % (drive, self)
2592 raise SCons.Errors.StopError, desc
2599 """Remove this file."""
2600 if self.exists() or self.islink():
2601 self.fs.unlink(self.path)
2605 def do_duplicate(self, src):
2607 Unlink(self, None, None)
2608 e = Link(self, src, None)
2609 if isinstance(e, SCons.Errors.BuildError):
2610 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2611 raise SCons.Errors.StopError, desc
2613 # The Link() action may or may not have actually
2614 # created the file, depending on whether the -n
2615 # option was used or not. Delete the _exists and
2616 # _rexists attributes so they can be reevaluated.
2619 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2623 return self._memo['exists']
2626 # Duplicate from source path if we are set up to do this.
2627 if self.duplicate and not self.is_derived() and not self.linked:
2628 src = self.srcnode()
2630 # At this point, src is meant to be copied in a variant directory.
2632 if src.abspath != self.abspath:
2634 self.do_duplicate(src)
2635 # Can't return 1 here because the duplication might
2636 # not actually occur if the -n option is being used.
2638 # The source file does not exist. Make sure no old
2639 # copy remains in the variant directory.
2640 if Base.exists(self) or self.islink():
2641 self.fs.unlink(self.path)
2642 # Return None explicitly because the Base.exists() call
2643 # above will have cached its value if the file existed.
2644 self._memo['exists'] = None
2646 result = Base.exists(self)
2647 self._memo['exists'] = result
2651 # SIGNATURE SUBSYSTEM
2654 def get_max_drift_csig(self):
2656 Returns the content signature currently stored for this node
2657 if it's been unmodified longer than the max_drift value, or the
2658 max_drift value is 0. Returns None otherwise.
2660 old = self.get_stored_info()
2661 mtime = self.get_timestamp()
2664 max_drift = self.fs.max_drift
2666 if (time.time() - mtime) > max_drift:
2669 if n.timestamp and n.csig and n.timestamp == mtime:
2671 except AttributeError:
2673 elif max_drift == 0:
2675 csig = old.ninfo.csig
2676 except AttributeError:
2683 Generate a node's content signature, the digested signature
2687 cache - alternate node to use for the signature cache
2688 returns - the content signature
2690 ninfo = self.get_ninfo()
2693 except AttributeError:
2696 csig = self.get_max_drift_csig()
2700 contents = self.get_contents()
2702 # This can happen if there's actually a directory on-disk,
2703 # which can be the case if they've disabled disk checks,
2704 # or if an action with a File target actually happens to
2705 # create a same-named directory by mistake.
2708 csig = SCons.Util.MD5signature(contents)
2715 # DECISION SUBSYSTEM
2718 def builder_set(self, builder):
2719 SCons.Node.Node.builder_set(self, builder)
2720 self.changed_since_last_build = self.decide_target
2722 def changed_content(self, target, prev_ni):
2723 cur_csig = self.get_csig()
2725 return cur_csig != prev_ni.csig
2726 except AttributeError:
2729 def changed_state(self, target, prev_ni):
2730 return (self.state != SCons.Node.up_to_date)
2732 def changed_timestamp_then_content(self, target, prev_ni):
2733 if not self.changed_timestamp_match(target, prev_ni):
2735 self.get_ninfo().csig = prev_ni.csig
2736 except AttributeError:
2739 return self.changed_content(target, prev_ni)
2741 def changed_timestamp_newer(self, target, prev_ni):
2743 return self.get_timestamp() > target.get_timestamp()
2744 except AttributeError:
2747 def changed_timestamp_match(self, target, prev_ni):
2749 return self.get_timestamp() != prev_ni.timestamp
2750 except AttributeError:
2753 def decide_source(self, target, prev_ni):
2754 return target.get_build_env().decide_source(self, target, prev_ni)
2756 def decide_target(self, target, prev_ni):
2757 return target.get_build_env().decide_target(self, target, prev_ni)
2759 # Initialize this Node's decider function to decide_source() because
2760 # every file is a source file until it has a Builder attached...
2761 changed_since_last_build = decide_source
2763 def is_up_to_date(self):
2765 if T: Trace('is_up_to_date(%s):' % self)
2766 if not self.exists():
2767 if T: Trace(' not self.exists():')
2768 # The file doesn't exist locally...
2771 # ...but there is one in a Repository...
2772 if not self.changed(r):
2773 if T: Trace(' changed(%s):' % r)
2774 # ...and it's even up-to-date...
2776 # ...and they'd like a local copy.
2777 e = LocalCopy(self, r, None)
2778 if isinstance(e, SCons.Errors.BuildError):
2784 if T: Trace(' None\n')
2788 if T: Trace(' self.exists(): %s\n' % r)
2791 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2795 return self._memo['rfile']
2799 if not self.exists():
2800 norm_name = _my_normcase(self.name)
2801 for dir in self.dir.get_all_rdirs():
2802 try: node = dir.entries[norm_name]
2803 except KeyError: node = dir.file_on_disk(self.name)
2804 if node and node.exists() and \
2805 (isinstance(node, File) or isinstance(node, Entry) \
2806 or not node.is_derived()):
2809 self._memo['rfile'] = result
2813 return str(self.rfile())
2815 def get_cachedir_csig(self):
2817 Fetch a Node's content signature for purposes of computing
2818 another Node's cachesig.
2820 This is a wrapper around the normal get_csig() method that handles
2821 the somewhat obscure case of using CacheDir with the -n option.
2822 Any files that don't exist would normally be "built" by fetching
2823 them from the cache, but the normal get_csig() method will try
2824 to open up the local file, which doesn't exist because the -n
2825 option meant we didn't actually pull the file from cachedir.
2826 But since the file *does* actually exist in the cachedir, we
2827 can use its contents for the csig.
2830 return self.cachedir_csig
2831 except AttributeError:
2834 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2835 if not self.exists() and cachefile and os.path.exists(cachefile):
2836 contents = open(cachefile, 'rb').read()
2837 self.cachedir_csig = SCons.Util.MD5signature(contents)
2839 self.cachedir_csig = self.get_csig()
2840 return self.cachedir_csig
2842 def get_cachedir_bsig(self):
2844 return self.cachesig
2845 except AttributeError:
2848 # Add the path to the cache signature, because multiple
2849 # targets built by the same action will all have the same
2850 # build signature, and we have to differentiate them somehow.
2851 children = self.children()
2852 sigs = map(lambda n: n.get_cachedir_csig(), children)
2853 executor = self.get_executor()
2854 sigs.append(SCons.Util.MD5signature(executor.get_contents()))
2855 sigs.append(self.path)
2856 self.cachesig = SCons.Util.MD5collect(sigs)
2857 return self.cachesig
2861 def get_default_fs():
2870 if SCons.Memoize.use_memoizer:
2871 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2873 memoizer_counters = []
2878 def filedir_lookup(self, p, fd=None):
2880 A helper method for find_file() that looks up a directory for
2881 a file we're trying to find. This only creates the Dir Node if
2882 it exists on-disk, since if the directory doesn't exist we know
2883 we won't find any files in it... :-)
2885 It would be more compact to just use this as a nested function
2886 with a default keyword argument (see the commented-out version
2887 below), but that doesn't work unless you have nested scopes,
2888 so we define it here just so this work under Python 1.5.2.
2891 fd = self.default_filedir
2892 dir, name = os.path.split(fd)
2893 drive, d = os.path.splitdrive(dir)
2894 if d in ('/', os.sep):
2895 return p.fs.get_root(drive).dir_on_disk(name)
2897 p = self.filedir_lookup(p, dir)
2900 norm_name = _my_normcase(name)
2902 node = p.entries[norm_name]
2904 return p.dir_on_disk(name)
2905 if isinstance(node, Dir):
2907 if isinstance(node, Entry):
2908 node.must_be_same(Dir)
2912 def _find_file_key(self, filename, paths, verbose=None):
2913 return (filename, paths)
2915 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2917 def find_file(self, filename, paths, verbose=None):
2919 find_file(str, [Dir()]) -> [nodes]
2921 filename - a filename to find
2922 paths - a list of directory path *nodes* to search in. Can be
2923 represented as a list, a tuple, or a callable that is
2924 called with no arguments and returns the list or tuple.
2926 returns - the node created from the found file.
2928 Find a node corresponding to either a derived file or a file
2929 that exists already.
2931 Only the first file found is returned, and none is returned
2932 if no file is found.
2934 memo_key = self._find_file_key(filename, paths)
2936 memo_dict = self._memo['find_file']
2939 self._memo['find_file'] = memo_dict
2942 return memo_dict[memo_key]
2947 if not SCons.Util.is_String(verbose):
2948 verbose = "find_file"
2949 if not callable(verbose):
2950 verbose = ' %s: ' % verbose
2951 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2953 verbose = lambda x: x
2955 filedir, filename = os.path.split(filename)
2957 # More compact code that we can't use until we drop
2958 # support for Python 1.5.2:
2960 #def filedir_lookup(p, fd=filedir):
2962 # A helper function that looks up a directory for a file
2963 # we're trying to find. This only creates the Dir Node
2964 # if it exists on-disk, since if the directory doesn't
2965 # exist we know we won't find any files in it... :-)
2967 # dir, name = os.path.split(fd)
2969 # p = filedir_lookup(p, dir)
2972 # norm_name = _my_normcase(name)
2974 # node = p.entries[norm_name]
2976 # return p.dir_on_disk(name)
2977 # if isinstance(node, Dir):
2979 # if isinstance(node, Entry):
2980 # node.must_be_same(Dir)
2982 # if isinstance(node, Dir) or isinstance(node, Entry):
2985 #paths = filter(None, map(filedir_lookup, paths))
2987 self.default_filedir = filedir
2988 paths = filter(None, map(self.filedir_lookup, paths))
2992 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2993 node, d = dir.srcdir_find_file(filename)
2995 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2999 memo_dict[memo_key] = result
3003 find_file = FileFinder().find_file
3006 def invalidate_node_memos(targets):
3008 Invalidate the memoized values of all Nodes (files or directories)
3009 that are associated with the given entries. Has been added to
3010 clear the cache of nodes affected by a direct execution of an
3011 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3012 inconsistent if the action is run through Execute(). The argument
3013 `targets` can be a single Node object or filename, or a sequence
3016 from traceback import extract_stack
3018 # First check if the cache really needs to be flushed. Only
3019 # actions run in the SConscript with Execute() seem to be
3020 # affected. XXX The way to check if Execute() is in the stacktrace
3021 # is a very dirty hack and should be replaced by a more sensible
3024 tb = extract_stack()
3026 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3028 if not must_invalidate:
3031 if not SCons.Util.is_List(targets):
3034 for entry in targets:
3035 # If the target is a Node object, clear the cache. If it is a
3036 # filename, look up potentially existing Node object first.
3038 entry.clear_memoized_values()
3039 except AttributeError:
3040 # Not a Node object, try to look up Node by filename. XXX
3041 # This creates Node objects even for those filenames which
3042 # do not correspond to an existing Node object.
3043 node = get_default_fs().Entry(entry)
3045 node.clear_memoized_values()