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__"
38 from itertools import izip
55 # TODO(2.2): Remove when 2.3 becomes the minimal supported version.
58 except AttributeError:
59 codecs.BOM_UTF8 = '\xef\xbb\xbf'
62 except AttributeError:
63 if sys.byteorder == 'little':
64 codecs.BOM_UTF16 = '\xff\xfe'
66 codecs.BOM_UTF16 = '\xfe\xff'
69 from SCons.Debug import logInstanceCreation
73 import SCons.Node.Alias
78 from SCons.Debug import Trace
83 class EntryProxyAttributeError(AttributeError):
85 An AttributeError subclass for recording and displaying the name
86 of the underlying Entry involved in an AttributeError exception.
88 def __init__(self, entry_proxy, attribute):
89 AttributeError.__init__(self)
90 self.entry_proxy = entry_proxy
91 self.attribute = attribute
93 entry = self.entry_proxy.get()
94 fmt = "%s instance %s has no attribute %s"
95 return fmt % (entry.__class__.__name__,
99 # The max_drift value: by default, use a cached signature value for
100 # any file that's been untouched for more than two days.
101 default_max_drift = 2*24*60*60
104 # We stringify these file system Nodes a lot. Turning a file system Node
105 # into a string is non-trivial, because the final string representation
106 # can depend on a lot of factors: whether it's a derived target or not,
107 # whether it's linked to a repository or source directory, and whether
108 # there's duplication going on. The normal technique for optimizing
109 # calculations like this is to memoize (cache) the string value, so you
110 # only have to do the calculation once.
112 # A number of the above factors, however, can be set after we've already
113 # been asked to return a string for a Node, because a Repository() or
114 # VariantDir() call or the like may not occur until later in SConscript
115 # files. So this variable controls whether we bother trying to save
116 # string values for Nodes. The wrapper interface can set this whenever
117 # they're done mucking with Repository and VariantDir and the other stuff,
118 # to let this module know it can start returning saved string values
123 def save_strings(val):
128 # Avoid unnecessary function calls by recording a Boolean value that
129 # tells us whether or not os.path.splitdrive() actually does anything
130 # on this system, and therefore whether we need to bother calling it
131 # when looking up path names in various methods below.
136 def initialize_do_splitdrive():
138 drive, path = os.path.splitdrive('X:/foo')
139 do_splitdrive = not not drive
141 initialize_do_splitdrive()
145 needs_normpath_check = None
147 def initialize_normpath_check():
149 Initialize the normpath_check regular expression.
151 This function is used by the unit tests to re-initialize the pattern
152 when testing for behavior with different values of os.sep.
154 global needs_normpath_check
156 pattern = r'.*/|\.$|\.\.$'
158 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
159 needs_normpath_check = re.compile(pattern)
161 initialize_normpath_check()
164 # SCons.Action objects for interacting with the outside world.
166 # The Node.FS methods in this module should use these actions to
167 # create and/or remove files and directories; they should *not* use
168 # os.{link,symlink,unlink,mkdir}(), etc., directly.
170 # Using these SCons.Action objects ensures that descriptions of these
171 # external activities are properly displayed, that the displays are
172 # suppressed when the -s (silent) option is used, and (most importantly)
173 # the actions are disabled when the the -n option is used, in which case
174 # there should be *no* changes to the external file system(s)...
177 if hasattr(os, 'link'):
178 def _hardlink_func(fs, src, dst):
179 # If the source is a symlink, we can't just hard-link to it
180 # because a relative symlink may point somewhere completely
181 # different. We must disambiguate the symlink and then
182 # hard-link the final destination file.
183 while fs.islink(src):
184 link = fs.readlink(src)
185 if not os.path.isabs(link):
188 src = os.path.join(os.path.dirname(src), link)
191 _hardlink_func = None
193 if hasattr(os, 'symlink'):
194 def _softlink_func(fs, src, dst):
197 _softlink_func = None
199 def _copy_func(fs, src, dest):
200 shutil.copy2(src, dest)
202 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
205 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
206 'hard-copy', 'soft-copy', 'copy']
208 Link_Funcs = [] # contains the callables of the specified duplication style
210 def set_duplicate(duplicate):
211 # Fill in the Link_Funcs list according to the argument
212 # (discarding those not available on the platform).
214 # Set up the dictionary that maps the argument names to the
215 # underlying implementations. We do this inside this function,
216 # not in the top-level module code, so that we can remap os.link
217 # and os.symlink for testing purposes.
219 'hard' : _hardlink_func,
220 'soft' : _softlink_func,
224 if not duplicate in Valid_Duplicates:
225 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
226 "should be in Valid_Duplicates")
229 for func in string.split(duplicate,'-'):
231 Link_Funcs.append(link_dict[func])
233 def LinkFunc(target, source, env):
234 # Relative paths cause problems with symbolic links, so
235 # we use absolute paths, which may be a problem for people
236 # who want to move their soft-linked src-trees around. Those
237 # people should use the 'hard-copy' mode, softlinks cannot be
238 # used for that; at least I have no idea how ...
239 src = source[0].abspath
240 dest = target[0].abspath
241 dir, file = os.path.split(dest)
242 if dir and not target[0].fs.isdir(dir):
245 # Set a default order of link functions.
246 set_duplicate('hard-soft-copy')
248 # Now link the files with the previously specified order.
249 for func in Link_Funcs:
253 except (IOError, OSError):
254 # An OSError indicates something happened like a permissions
255 # problem or an attempt to symlink across file-system
256 # boundaries. An IOError indicates something like the file
257 # not existing. In either case, keeping trying additional
258 # functions in the list and only raise an error if the last
260 if func == Link_Funcs[-1]:
261 # exception of the last link method (copy) are fatal
265 Link = SCons.Action.Action(LinkFunc, None)
266 def LocalString(target, source, env):
267 return 'Local copy of %s from %s' % (target[0], source[0])
269 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
271 def UnlinkFunc(target, source, env):
273 t.fs.unlink(t.abspath)
276 Unlink = SCons.Action.Action(UnlinkFunc, None)
278 def MkdirFunc(target, source, env):
281 t.fs.mkdir(t.abspath)
284 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
288 def get_MkdirBuilder():
290 if MkdirBuilder is None:
292 import SCons.Defaults
293 # "env" will get filled in by Executor.get_build_env()
294 # calling SCons.Defaults.DefaultEnvironment() when necessary.
295 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
299 target_scanner = SCons.Defaults.DirEntryScanner,
300 name = "MkdirBuilder")
308 DefaultSCCSBuilder = None
309 DefaultRCSBuilder = None
311 def get_DefaultSCCSBuilder():
312 global DefaultSCCSBuilder
313 if DefaultSCCSBuilder is None:
315 # "env" will get filled in by Executor.get_build_env()
316 # calling SCons.Defaults.DefaultEnvironment() when necessary.
317 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
318 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
320 name = "DefaultSCCSBuilder")
321 return DefaultSCCSBuilder
323 def get_DefaultRCSBuilder():
324 global DefaultRCSBuilder
325 if DefaultRCSBuilder is None:
327 # "env" will get filled in by Executor.get_build_env()
328 # calling SCons.Defaults.DefaultEnvironment() when necessary.
329 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
330 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
332 name = "DefaultRCSBuilder")
333 return DefaultRCSBuilder
335 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
336 _is_cygwin = sys.platform == "cygwin"
337 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
342 return string.upper(x)
347 def __init__(self, type, do, ignore):
353 self.__call__ = self.do
354 def set_ignore(self):
355 self.__call__ = self.ignore
357 if self.type in list:
362 def do_diskcheck_match(node, predicate, errorfmt):
365 # If calling the predicate() cached a None value from stat(),
366 # remove it so it doesn't interfere with later attempts to
367 # build this Node as we walk the DAG. (This isn't a great way
368 # to do this, we're reaching into an interface that doesn't
369 # really belong to us, but it's all about performance, so
370 # for now we'll just document the dependency...)
371 if node._memo['stat'] is None:
372 del node._memo['stat']
373 except (AttributeError, KeyError):
376 raise TypeError, errorfmt % node.abspath
378 def ignore_diskcheck_match(node, predicate, errorfmt):
381 def do_diskcheck_rcs(node, name):
383 rcs_dir = node.rcs_dir
384 except AttributeError:
385 if node.entry_exists_on_disk('RCS'):
386 rcs_dir = node.Dir('RCS')
389 node.rcs_dir = rcs_dir
391 return rcs_dir.entry_exists_on_disk(name+',v')
394 def ignore_diskcheck_rcs(node, name):
397 def do_diskcheck_sccs(node, name):
399 sccs_dir = node.sccs_dir
400 except AttributeError:
401 if node.entry_exists_on_disk('SCCS'):
402 sccs_dir = node.Dir('SCCS')
405 node.sccs_dir = sccs_dir
407 return sccs_dir.entry_exists_on_disk('s.'+name)
410 def ignore_diskcheck_sccs(node, name):
413 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
414 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
415 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
423 def set_diskcheck(list):
424 for dc in diskcheckers:
427 def diskcheck_types():
428 return map(lambda dc: dc.type, diskcheckers)
432 class EntryProxy(SCons.Util.Proxy):
433 def __get_abspath(self):
435 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
436 entry.name + "_abspath")
438 def __get_filebase(self):
439 name = self.get().name
440 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
443 def __get_suffix(self):
444 name = self.get().name
445 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
448 def __get_file(self):
449 name = self.get().name
450 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
452 def __get_base_path(self):
453 """Return the file's directory and file name, with the
456 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
457 entry.name + "_base")
459 def __get_posix_path(self):
460 """Return the path with / as the path separator,
461 regardless of platform."""
466 r = string.replace(entry.get_path(), os.sep, '/')
467 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
469 def __get_windows_path(self):
470 """Return the path with \ as the path separator,
471 regardless of platform."""
476 r = string.replace(entry.get_path(), os.sep, '\\')
477 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
479 def __get_srcnode(self):
480 return EntryProxy(self.get().srcnode())
482 def __get_srcdir(self):
483 """Returns the directory containing the source node linked to this
484 node via VariantDir(), or the directory of this node if not linked."""
485 return EntryProxy(self.get().srcnode().dir)
487 def __get_rsrcnode(self):
488 return EntryProxy(self.get().srcnode().rfile())
490 def __get_rsrcdir(self):
491 """Returns the directory containing the source node linked to this
492 node via VariantDir(), or the directory of this node if not linked."""
493 return EntryProxy(self.get().srcnode().rfile().dir)
496 return EntryProxy(self.get().dir)
498 dictSpecialAttrs = { "base" : __get_base_path,
499 "posix" : __get_posix_path,
500 "windows" : __get_windows_path,
501 "win32" : __get_windows_path,
502 "srcpath" : __get_srcnode,
503 "srcdir" : __get_srcdir,
505 "abspath" : __get_abspath,
506 "filebase" : __get_filebase,
507 "suffix" : __get_suffix,
509 "rsrcpath" : __get_rsrcnode,
510 "rsrcdir" : __get_rsrcdir,
513 def __getattr__(self, name):
514 # This is how we implement the "special" attributes
515 # such as base, posix, srcdir, etc.
517 attr_function = self.dictSpecialAttrs[name]
520 attr = SCons.Util.Proxy.__getattr__(self, name)
521 except AttributeError, e:
522 # Raise our own AttributeError subclass with an
523 # overridden __str__() method that identifies the
524 # name of the entry that caused the exception.
525 raise EntryProxyAttributeError(self, name)
528 return attr_function(self)
530 class Base(SCons.Node.Node):
531 """A generic class for file system entries. This class is for
532 when we don't know yet whether the entry being looked up is a file
533 or a directory. Instances of this class can morph into either
534 Dir or File objects by a later, more precise lookup.
536 Note: this class does not define __cmp__ and __hash__ for
537 efficiency reasons. SCons does a lot of comparing of
538 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
539 as fast as possible, which means we want to use Python's built-in
540 object identity comparisons.
543 memoizer_counters = []
545 def __init__(self, name, directory, fs):
546 """Initialize a generic Node.FS.Base object.
548 Call the superclass initialization, take care of setting up
549 our relative and absolute paths, identify our parent
550 directory, and indicate that this node should use
552 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
553 SCons.Node.Node.__init__(self)
556 self.suffix = SCons.Util.splitext(name)[1]
559 assert directory, "A directory must be provided"
561 self.abspath = directory.entry_abspath(name)
562 self.labspath = directory.entry_labspath(name)
563 if directory.path == '.':
566 self.path = directory.entry_path(name)
567 if directory.tpath == '.':
570 self.tpath = directory.entry_tpath(name)
571 self.path_elements = directory.path_elements + [self]
574 self.cwd = None # will hold the SConscript directory for target nodes
575 self.duplicate = directory.duplicate
577 def str_for_display(self):
578 return '"' + self.__str__() + '"'
580 def must_be_same(self, klass):
582 This node, which already existed, is being looked up as the
583 specified klass. Raise an exception if it isn't.
585 if isinstance(self, klass) or klass is Entry:
587 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
588 (self.__class__.__name__, self.path, klass.__name__)
593 def get_suffix(self):
600 """A Node.FS.Base object's string representation is its path
604 return self._save_str()
605 return self._get_str()
607 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
611 return self._memo['_save_str']
614 result = self._get_str()
615 self._memo['_save_str'] = result
620 if self.duplicate or self.is_derived():
621 return self.get_path()
622 srcnode = self.srcnode()
623 if srcnode.stat() is None and self.stat() is not None:
624 result = self.get_path()
626 result = srcnode.get_path()
628 # We're not at the point where we're saving the string string
629 # representations of FS Nodes (because we haven't finished
630 # reading the SConscript files and need to have str() return
631 # things relative to them). That also means we can't yet
632 # cache values returned (or not returned) by stat(), since
633 # Python code in the SConscript files might still create
634 # or otherwise affect the on-disk file. So get rid of the
635 # values that the underlying stat() method saved.
636 try: del self._memo['stat']
637 except KeyError: pass
638 if self is not srcnode:
639 try: del srcnode._memo['stat']
640 except KeyError: pass
645 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
648 try: return self._memo['stat']
649 except KeyError: pass
650 try: result = self.fs.stat(self.abspath)
651 except os.error: result = None
652 self._memo['stat'] = result
656 return self.stat() is not None
659 return self.rfile().exists()
663 if st: return st[stat.ST_MTIME]
668 if st: return st[stat.ST_SIZE]
673 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
677 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
679 if hasattr(os, 'symlink'):
681 try: st = self.fs.lstat(self.abspath)
682 except os.error: return 0
683 return stat.S_ISLNK(st[stat.ST_MODE])
686 return 0 # no symlinks
688 def is_under(self, dir):
692 return self.dir.is_under(dir)
698 """If this node is in a build path, return the node
699 corresponding to its source file. Otherwise, return
702 srcdir_list = self.dir.srcdir_list()
704 srcnode = srcdir_list[0].Entry(self.name)
705 srcnode.must_be_same(self.__class__)
709 def get_path(self, dir=None):
710 """Return path relative to the current working directory of the
711 Node.FS.Base object that owns us."""
713 dir = self.fs.getcwd()
716 path_elems = self.path_elements
717 try: i = path_elems.index(dir)
718 except ValueError: pass
719 else: path_elems = path_elems[i+1:]
720 path_elems = map(lambda n: n.name, path_elems)
721 return string.join(path_elems, os.sep)
723 def set_src_builder(self, builder):
724 """Set the source code builder for this node."""
725 self.sbuilder = builder
726 if not self.has_builder():
727 self.builder_set(builder)
729 def src_builder(self):
730 """Fetch the source code builder for this node.
732 If there isn't one, we cache the source code builder specified
733 for the directory (which in turn will cache the value from its
734 parent directory, and so on up to the file system root).
738 except AttributeError:
739 scb = self.dir.src_builder()
743 def get_abspath(self):
744 """Get the absolute path of the file."""
747 def for_signature(self):
748 # Return just our name. Even an absolute path would not work,
749 # because that can change thanks to symlinks or remapped network
753 def get_subst_proxy(self):
756 except AttributeError:
757 ret = EntryProxy(self)
761 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
764 Generates a target entry that corresponds to this entry (usually
765 a source file) with the specified prefix and suffix.
767 Note that this method can be overridden dynamically for generated
768 files that need different behavior. See Tool/swig.py for
771 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
773 def _Rfindalldirs_key(self, pathlist):
776 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
778 def Rfindalldirs(self, pathlist):
780 Return all of the directories for a given path list, including
781 corresponding "backing" directories in any repositories.
783 The Node lookups are relative to this Node (typically a
784 directory), so memoizing result saves cycles from looking
785 up the same path for each target in a given directory.
788 memo_dict = self._memo['Rfindalldirs']
791 self._memo['Rfindalldirs'] = memo_dict
794 return memo_dict[pathlist]
798 create_dir_relative_to_self = self.Dir
800 for path in pathlist:
801 if isinstance(path, SCons.Node.Node):
804 dir = create_dir_relative_to_self(path)
805 result.extend(dir.get_all_rdirs())
807 memo_dict[pathlist] = result
811 def RDirs(self, pathlist):
812 """Search for a list of directories in the Repository list."""
813 cwd = self.cwd or self.fs._cwd
814 return cwd.Rfindalldirs(pathlist)
816 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
820 return self._memo['rentry']
824 if not self.exists():
825 norm_name = _my_normcase(self.name)
826 for dir in self.dir.get_all_rdirs():
828 node = dir.entries[norm_name]
830 if dir.entry_exists_on_disk(self.name):
831 result = dir.Entry(self.name)
833 self._memo['rentry'] = result
836 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
840 """This is the class for generic Node.FS entries--that is, things
841 that could be a File or a Dir, but we're just not sure yet.
842 Consequently, the methods in this class really exist just to
843 transform their associated object into the right class when the
844 time comes, and then call the same-named method in the transformed
847 def diskcheck_match(self):
850 def disambiguate(self, must_exist=None):
857 self.__class__ = File
861 # There was nothing on-disk at this location, so look in
864 # We can't just use self.srcnode() straight away because
865 # that would create an actual Node for this file in the src
866 # directory, and there might not be one. Instead, use the
867 # dir_on_disk() method to see if there's something on-disk
868 # with that name, in which case we can go ahead and call
869 # self.srcnode() to create the right type of entry.
870 srcdir = self.dir.srcnode()
871 if srcdir != self.dir and \
872 srcdir.entry_exists_on_disk(self.name) and \
873 self.srcnode().isdir():
877 msg = "No such file or directory: '%s'" % self.abspath
878 raise SCons.Errors.UserError, msg
880 self.__class__ = File
886 """We're a generic Entry, but the caller is actually looking for
887 a File at this point, so morph into one."""
888 self.__class__ = File
891 return File.rfile(self)
893 def scanner_key(self):
894 return self.get_suffix()
896 def get_contents(self):
897 """Fetch the contents of the entry. Returns the exact binary
898 contents of the file."""
900 self = self.disambiguate(must_exist=1)
901 except SCons.Errors.UserError:
902 # There was nothing on disk with which to disambiguate
903 # this entry. Leave it as an Entry, but return a null
904 # string so calls to get_contents() in emitters and the
905 # like (e.g. in qt.py) don't have to disambiguate by hand
906 # or catch the exception.
909 return self.get_contents()
911 def get_text_contents(self):
912 """Fetch the decoded text contents of a Unicode encoded Entry.
914 Since this should return the text contents from the file
915 system, we check to see into what sort of subclass we should
918 self = self.disambiguate(must_exist=1)
919 except SCons.Errors.UserError:
920 # There was nothing on disk with which to disambiguate
921 # this entry. Leave it as an Entry, but return a null
922 # string so calls to get_text_contents() in emitters and
923 # the like (e.g. in qt.py) don't have to disambiguate by
924 # hand or catch the exception.
927 return self.get_text_contents()
929 def must_be_same(self, klass):
930 """Called to make sure a Node is a Dir. Since we're an
931 Entry, we can morph into one."""
932 if self.__class__ is not klass:
933 self.__class__ = klass
937 # The following methods can get called before the Taskmaster has
938 # had a chance to call disambiguate() directly to see if this Entry
939 # should really be a Dir or a File. We therefore use these to call
940 # disambiguate() transparently (from our caller's point of view).
942 # Right now, this minimal set of methods has been derived by just
943 # looking at some of the methods that will obviously be called early
944 # in any of the various Taskmasters' calling sequences, and then
945 # empirically figuring out which additional methods are necessary
946 # to make various tests pass.
949 """Return if the Entry exists. Check the file system to see
950 what we should turn into first. Assume a file if there's no
952 return self.disambiguate().exists()
954 def rel_path(self, other):
955 d = self.disambiguate()
956 if d.__class__ is Entry:
957 raise "rel_path() could not disambiguate File/Dir"
958 return d.rel_path(other)
961 return self.disambiguate().new_ninfo()
963 def changed_since_last_build(self, target, prev_ni):
964 return self.disambiguate().changed_since_last_build(target, prev_ni)
966 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
967 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
969 def get_subst_proxy(self):
970 return self.disambiguate().get_subst_proxy()
972 # This is for later so we can differentiate between Entry the class and Entry
973 # the method of the FS class.
979 if SCons.Memoize.use_memoizer:
980 __metaclass__ = SCons.Memoize.Memoized_Metaclass
982 # This class implements an abstraction layer for operations involving
983 # a local file system. Essentially, this wraps any function in
984 # the os, os.path or shutil modules that we use to actually go do
985 # anything with or to the local file system.
987 # Note that there's a very good chance we'll refactor this part of
988 # the architecture in some way as we really implement the interface(s)
989 # for remote file system Nodes. For example, the right architecture
990 # might be to have this be a subclass instead of a base class.
991 # Nevertheless, we're using this as a first step in that direction.
993 # We're not using chdir() yet because the calling subclass method
994 # needs to use os.chdir() directly to avoid recursion. Will we
995 # really need this one?
996 #def chdir(self, path):
997 # return os.chdir(path)
998 def chmod(self, path, mode):
999 return os.chmod(path, mode)
1000 def copy(self, src, dst):
1001 return shutil.copy(src, dst)
1002 def copy2(self, src, dst):
1003 return shutil.copy2(src, dst)
1004 def exists(self, path):
1005 return os.path.exists(path)
1006 def getmtime(self, path):
1007 return os.path.getmtime(path)
1008 def getsize(self, path):
1009 return os.path.getsize(path)
1010 def isdir(self, path):
1011 return os.path.isdir(path)
1012 def isfile(self, path):
1013 return os.path.isfile(path)
1014 def link(self, src, dst):
1015 return os.link(src, dst)
1016 def lstat(self, path):
1017 return os.lstat(path)
1018 def listdir(self, path):
1019 return os.listdir(path)
1020 def makedirs(self, path):
1021 return os.makedirs(path)
1022 def mkdir(self, path):
1023 return os.mkdir(path)
1024 def rename(self, old, new):
1025 return os.rename(old, new)
1026 def stat(self, path):
1027 return os.stat(path)
1028 def symlink(self, src, dst):
1029 return os.symlink(src, dst)
1030 def open(self, path):
1032 def unlink(self, path):
1033 return os.unlink(path)
1035 if hasattr(os, 'symlink'):
1036 def islink(self, path):
1037 return os.path.islink(path)
1039 def islink(self, path):
1040 return 0 # no symlinks
1042 if hasattr(os, 'readlink'):
1043 def readlink(self, file):
1044 return os.readlink(file)
1046 def readlink(self, file):
1051 # # Skeleton for the obvious methods we might need from the
1052 # # abstraction layer for a remote filesystem.
1053 # def upload(self, local_src, remote_dst):
1055 # def download(self, remote_src, local_dst):
1061 memoizer_counters = []
1063 def __init__(self, path = None):
1064 """Initialize the Node.FS subsystem.
1066 The supplied path is the top of the source tree, where we
1067 expect to find the top-level build file. If no path is
1068 supplied, the current directory is the default.
1070 The path argument must be a valid absolute path.
1072 if __debug__: logInstanceCreation(self, 'Node.FS')
1077 self.SConstruct_dir = None
1078 self.max_drift = default_max_drift
1082 self.pathTop = os.getcwd()
1085 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1087 self.Top = self.Dir(self.pathTop)
1089 self.Top.tpath = '.'
1090 self._cwd = self.Top
1092 DirNodeInfo.fs = self
1093 FileNodeInfo.fs = self
1095 def set_SConstruct_dir(self, dir):
1096 self.SConstruct_dir = dir
1098 def get_max_drift(self):
1099 return self.max_drift
1101 def set_max_drift(self, max_drift):
1102 self.max_drift = max_drift
1107 def chdir(self, dir, change_os_dir=0):
1108 """Change the current working directory for lookups.
1109 If change_os_dir is true, we will also change the "real" cwd
1117 os.chdir(dir.abspath)
1122 def get_root(self, drive):
1124 Returns the root directory for the specified drive, creating
1127 drive = _my_normcase(drive)
1129 return self.Root[drive]
1131 root = RootDir(drive, self)
1132 self.Root[drive] = root
1134 self.Root[self.defaultDrive] = root
1135 elif drive == self.defaultDrive:
1136 self.Root[''] = root
1139 def _lookup(self, p, directory, fsclass, create=1):
1141 The generic entry point for Node lookup with user-supplied data.
1143 This translates arbitrary input into a canonical Node.FS object
1144 of the specified fsclass. The general approach for strings is
1145 to turn it into a fully normalized absolute path and then call
1146 the root directory's lookup_abs() method for the heavy lifting.
1148 If the path name begins with '#', it is unconditionally
1149 interpreted relative to the top-level directory of this FS. '#'
1150 is treated as a synonym for the top-level SConstruct directory,
1151 much like '~' is treated as a synonym for the user's home
1152 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1153 to the 'foo' subdirectory underneath the top-level SConstruct
1156 If the path name is relative, then the path is looked up relative
1157 to the specified directory, or the current directory (self._cwd,
1158 typically the SConscript directory) if the specified directory
1161 if isinstance(p, Base):
1162 # It's already a Node.FS object. Make sure it's the right
1164 p.must_be_same(fsclass)
1166 # str(p) in case it's something like a proxy object
1169 initial_hash = (p[0:1] == '#')
1171 # There was an initial '#', so we strip it and override
1172 # whatever directory they may have specified with the
1173 # top-level SConstruct directory.
1175 directory = self.Top
1177 if directory and not isinstance(directory, Dir):
1178 directory = self.Dir(directory)
1181 drive, p = os.path.splitdrive(p)
1185 # This causes a naked drive letter to be treated as a synonym
1186 # for the root directory on that drive.
1188 absolute = os.path.isabs(p)
1190 needs_normpath = needs_normpath_check.match(p)
1192 if initial_hash or not absolute:
1193 # This is a relative lookup, either to the top-level
1194 # SConstruct directory (because of the initial '#') or to
1195 # the current directory (the path name is not absolute).
1196 # Add the string to the appropriate directory lookup path,
1197 # after which the whole thing gets normalized.
1199 directory = self._cwd
1201 p = directory.labspath + '/' + p
1203 p = directory.labspath
1206 p = os.path.normpath(p)
1208 if drive or absolute:
1209 root = self.get_root(drive)
1212 directory = self._cwd
1213 root = directory.root
1216 p = string.replace(p, os.sep, '/')
1217 return root._lookup_abs(p, fsclass, create)
1219 def Entry(self, name, directory = None, create = 1):
1220 """Look up or create a generic Entry node with the specified name.
1221 If the name is a relative path (begins with ./, ../, or a file
1222 name), then it is looked up relative to the supplied directory
1223 node, or to the top level directory of the FS (supplied at
1224 construction time) if no directory is supplied.
1226 return self._lookup(name, directory, Entry, create)
1228 def File(self, name, directory = None, create = 1):
1229 """Look up or create a File node with the specified name. If
1230 the name is a relative path (begins with ./, ../, or a file name),
1231 then it is looked up relative to the supplied directory node,
1232 or to the top level directory of the FS (supplied at construction
1233 time) if no directory is supplied.
1235 This method will raise TypeError if a directory is found at the
1238 return self._lookup(name, directory, File, create)
1240 def Dir(self, name, directory = None, create = True):
1241 """Look up or create a Dir node with the specified name. If
1242 the name is a relative path (begins with ./, ../, or a file name),
1243 then it is looked up relative to the supplied directory node,
1244 or to the top level directory of the FS (supplied at construction
1245 time) if no directory is supplied.
1247 This method will raise TypeError if a normal file is found at the
1250 return self._lookup(name, directory, Dir, create)
1252 def VariantDir(self, variant_dir, src_dir, duplicate=1):
1253 """Link the supplied variant directory to the source directory
1254 for purposes of building files."""
1256 if not isinstance(src_dir, SCons.Node.Node):
1257 src_dir = self.Dir(src_dir)
1258 if not isinstance(variant_dir, SCons.Node.Node):
1259 variant_dir = self.Dir(variant_dir)
1260 if src_dir.is_under(variant_dir):
1261 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1262 if variant_dir.srcdir:
1263 if variant_dir.srcdir == src_dir:
1264 return # We already did this.
1265 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1266 variant_dir.link(src_dir, duplicate)
1268 def Repository(self, *dirs):
1269 """Specify Repository directories to search."""
1271 if not isinstance(d, SCons.Node.Node):
1273 self.Top.addRepository(d)
1275 def variant_dir_target_climb(self, orig, dir, tail):
1276 """Create targets in corresponding variant directories
1278 Climb the directory tree, and look up path names
1279 relative to any linked variant directories we find.
1281 Even though this loops and walks up the tree, we don't memoize
1282 the return value because this is really only used to process
1283 the command-line targets.
1287 fmt = "building associated VariantDir targets: %s"
1290 for bd in dir.variant_dirs:
1291 if start_dir.is_under(bd):
1292 # If already in the build-dir location, don't reflect
1293 return [orig], fmt % str(orig)
1294 p = apply(os.path.join, [bd.path] + tail)
1295 targets.append(self.Entry(p))
1296 tail = [dir.name] + tail
1299 message = fmt % string.join(map(str, targets))
1300 return targets, message
1302 def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1306 This is mainly a shim layer
1310 return cwd.glob(pathname, ondisk, source, strings)
1312 class DirNodeInfo(SCons.Node.NodeInfoBase):
1313 # This should get reset by the FS initialization.
1314 current_version_id = 1
1318 def str_to_node(self, s):
1322 drive, s = os.path.splitdrive(s)
1324 root = self.fs.get_root(drive)
1325 if not os.path.isabs(s):
1326 s = top.labspath + '/' + s
1327 return root._lookup_abs(s, Entry)
1329 class DirBuildInfo(SCons.Node.BuildInfoBase):
1330 current_version_id = 1
1332 glob_magic_check = re.compile('[*?[]')
1334 def has_glob_magic(s):
1335 return glob_magic_check.search(s) is not None
1338 """A class for directories in a file system.
1341 memoizer_counters = []
1343 NodeInfo = DirNodeInfo
1344 BuildInfo = DirBuildInfo
1346 def __init__(self, name, directory, fs):
1347 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1348 Base.__init__(self, name, directory, fs)
1352 """Turn a file system Node (either a freshly initialized directory
1353 object or a separate Entry object) into a proper directory object.
1355 Set up this directory's entries and hook it into the file
1356 system tree. Specify that directories (this Node) don't use
1357 signatures for calculating whether they're current.
1360 self.repositories = []
1364 self.entries['.'] = self
1365 self.entries['..'] = self.dir
1368 self._sconsign = None
1369 self.variant_dirs = []
1370 self.root = self.dir.root
1372 # Don't just reset the executor, replace its action list,
1373 # because it might have some pre-or post-actions that need to
1375 self.builder = get_MkdirBuilder()
1376 self.get_executor().set_action_list(self.builder.action)
1378 def diskcheck_match(self):
1379 diskcheck_match(self, self.isfile,
1380 "File %s found where directory expected.")
1382 def __clearRepositoryCache(self, duplicate=None):
1383 """Called when we change the repository(ies) for a directory.
1384 This clears any cached information that is invalidated by changing
1387 for node in self.entries.values():
1388 if node != self.dir:
1389 if node != self and isinstance(node, Dir):
1390 node.__clearRepositoryCache(duplicate)
1395 except AttributeError:
1397 if duplicate is not None:
1398 node.duplicate=duplicate
1400 def __resetDuplicate(self, node):
1402 node.duplicate = node.get_dir().duplicate
1404 def Entry(self, name):
1406 Looks up or creates an entry node named 'name' relative to
1409 return self.fs.Entry(name, self)
1411 def Dir(self, name, create=True):
1413 Looks up or creates a directory node named 'name' relative to
1416 return self.fs.Dir(name, self, create)
1418 def File(self, name):
1420 Looks up or creates a file node named 'name' relative to
1423 return self.fs.File(name, self)
1425 def _lookup_rel(self, name, klass, create=1):
1427 Looks up a *normalized* relative path name, relative to this
1430 This method is intended for use by internal lookups with
1431 already-normalized path data. For general-purpose lookups,
1432 use the Entry(), Dir() and File() methods above.
1434 This method does *no* input checking and will die or give
1435 incorrect results if it's passed a non-normalized path name (e.g.,
1436 a path containing '..'), an absolute path name, a top-relative
1437 ('#foo') path name, or any kind of object.
1439 name = self.entry_labspath(name)
1440 return self.root._lookup_abs(name, klass, create)
1442 def link(self, srcdir, duplicate):
1443 """Set this directory as the variant directory for the
1444 supplied source directory."""
1445 self.srcdir = srcdir
1446 self.duplicate = duplicate
1447 self.__clearRepositoryCache(duplicate)
1448 srcdir.variant_dirs.append(self)
1450 def getRepositories(self):
1451 """Returns a list of repositories for this directory.
1453 if self.srcdir and not self.duplicate:
1454 return self.srcdir.get_all_rdirs() + self.repositories
1455 return self.repositories
1457 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1459 def get_all_rdirs(self):
1461 return list(self._memo['get_all_rdirs'])
1469 for rep in dir.getRepositories():
1470 result.append(rep.Dir(fname))
1474 fname = dir.name + os.sep + fname
1477 self._memo['get_all_rdirs'] = list(result)
1481 def addRepository(self, dir):
1482 if dir != self and not dir in self.repositories:
1483 self.repositories.append(dir)
1485 self.__clearRepositoryCache()
1488 return self.entries['..']
1490 def _rel_path_key(self, other):
1493 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1495 def rel_path(self, other):
1496 """Return a path to "other" relative to this directory.
1499 # This complicated and expensive method, which constructs relative
1500 # paths between arbitrary Node.FS objects, is no longer used
1501 # by SCons itself. It was introduced to store dependency paths
1502 # in .sconsign files relative to the target, but that ended up
1503 # being significantly inefficient.
1505 # We're continuing to support the method because some SConstruct
1506 # files out there started using it when it was available, and
1507 # we're all about backwards compatibility..
1510 memo_dict = self._memo['rel_path']
1513 self._memo['rel_path'] = memo_dict
1516 return memo_dict[other]
1523 elif not other in self.path_elements:
1525 other_dir = other.get_dir()
1526 except AttributeError:
1529 if other_dir is None:
1532 dir_rel_path = self.rel_path(other_dir)
1533 if dir_rel_path == '.':
1536 result = dir_rel_path + os.sep + other.name
1538 i = self.path_elements.index(other) + 1
1540 path_elems = ['..'] * (len(self.path_elements) - i) \
1541 + map(lambda n: n.name, other.path_elements[i:])
1543 result = string.join(path_elems, os.sep)
1545 memo_dict[other] = result
1549 def get_env_scanner(self, env, kw={}):
1550 import SCons.Defaults
1551 return SCons.Defaults.DirEntryScanner
1553 def get_target_scanner(self):
1554 import SCons.Defaults
1555 return SCons.Defaults.DirEntryScanner
1557 def get_found_includes(self, env, scanner, path):
1558 """Return this directory's implicit dependencies.
1560 We don't bother caching the results because the scan typically
1561 shouldn't be requested more than once (as opposed to scanning
1562 .h file contents, which can be requested as many times as the
1563 files is #included by other files).
1567 # Clear cached info for this Dir. If we already visited this
1568 # directory on our walk down the tree (because we didn't know at
1569 # that point it was being used as the source for another Node)
1570 # then we may have calculated build signature before realizing
1571 # we had to scan the disk. Now that we have to, though, we need
1572 # to invalidate the old calculated signature so that any node
1573 # dependent on our directory structure gets one that includes
1574 # info about everything on disk.
1576 return scanner(self, env, path)
1579 # Taskmaster interface subsystem
1585 def build(self, **kw):
1586 """A null "builder" for directories."""
1588 if self.builder is not MkdirBuilder:
1589 apply(SCons.Node.Node.build, [self,], kw)
1596 """Create this directory, silently and without worrying about
1597 whether the builder is the default or not."""
1603 listDirs.append(parent)
1606 # Don't use while: - else: for this condition because
1607 # if so, then parent is None and has no .path attribute.
1608 raise SCons.Errors.StopError, parent.path
1611 for dirnode in listDirs:
1613 # Don't call dirnode.build(), call the base Node method
1614 # directly because we definitely *must* create this
1615 # directory. The dirnode.build() method will suppress
1616 # the build if it's the default builder.
1617 SCons.Node.Node.build(dirnode)
1618 dirnode.get_executor().nullify()
1619 # The build() action may or may not have actually
1620 # created the directory, depending on whether the -n
1621 # option was used or not. Delete the _exists and
1622 # _rexists attributes so they can be reevaluated.
1627 def multiple_side_effect_has_builder(self):
1629 return self.builder is not MkdirBuilder and self.has_builder()
1631 def alter_targets(self):
1632 """Return any corresponding targets in a variant directory.
1634 return self.fs.variant_dir_target_climb(self, self, [])
1636 def scanner_key(self):
1637 """A directory does not get scanned."""
1640 def get_text_contents(self):
1641 """We already emit things in text, so just return the binary
1643 return self.get_contents()
1645 def get_contents(self):
1646 """Return content signatures and names of all our children
1647 separated by new-lines. Ensure that the nodes are sorted."""
1649 name_cmp = lambda a, b: cmp(a.name, b.name)
1650 sorted_children = self.children()[:]
1651 sorted_children.sort(name_cmp)
1652 for node in sorted_children:
1653 contents.append('%s %s\n' % (node.get_csig(), node.name))
1654 return string.join(contents, '')
1657 """Compute the content signature for Directory nodes. In
1658 general, this is not needed and the content signature is not
1659 stored in the DirNodeInfo. However, if get_contents on a Dir
1660 node is called which has a child directory, the child
1661 directory should return the hash of its contents."""
1662 contents = self.get_contents()
1663 return SCons.Util.MD5signature(contents)
1665 def do_duplicate(self, src):
1668 changed_since_last_build = SCons.Node.Node.state_has_changed
1670 def is_up_to_date(self):
1671 """If any child is not up-to-date, then this directory isn't,
1673 if self.builder is not MkdirBuilder and not self.exists():
1675 up_to_date = SCons.Node.up_to_date
1676 for kid in self.children():
1677 if kid.get_state() > up_to_date:
1682 if not self.exists():
1683 norm_name = _my_normcase(self.name)
1684 for dir in self.dir.get_all_rdirs():
1685 try: node = dir.entries[norm_name]
1686 except KeyError: node = dir.dir_on_disk(self.name)
1687 if node and node.exists() and \
1688 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1693 """Return the .sconsign file info for this directory,
1694 creating it first if necessary."""
1695 if not self._sconsign:
1696 import SCons.SConsign
1697 self._sconsign = SCons.SConsign.ForDirectory(self)
1698 return self._sconsign
1701 """Dir has a special need for srcnode()...if we
1702 have a srcdir attribute set, then that *is* our srcnode."""
1705 return Base.srcnode(self)
1707 def get_timestamp(self):
1708 """Return the latest timestamp from among our children"""
1710 for kid in self.children():
1711 if kid.get_timestamp() > stamp:
1712 stamp = kid.get_timestamp()
1715 def entry_abspath(self, name):
1716 return self.abspath + os.sep + name
1718 def entry_labspath(self, name):
1719 return self.labspath + '/' + name
1721 def entry_path(self, name):
1722 return self.path + os.sep + name
1724 def entry_tpath(self, name):
1725 return self.tpath + os.sep + name
1727 def entry_exists_on_disk(self, name):
1729 d = self.on_disk_entries
1730 except AttributeError:
1733 entries = os.listdir(self.abspath)
1737 for entry in map(_my_normcase, entries):
1739 self.on_disk_entries = d
1740 if sys.platform == 'win32':
1741 name = _my_normcase(name)
1742 result = d.get(name)
1744 # Belt-and-suspenders for Windows: check directly for
1745 # 8.3 file names that don't show up in os.listdir().
1746 result = os.path.exists(self.abspath + os.sep + name)
1750 return d.has_key(name)
1752 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1754 def srcdir_list(self):
1756 return self._memo['srcdir_list']
1766 result.append(dir.srcdir.Dir(dirname))
1767 dirname = dir.name + os.sep + dirname
1770 self._memo['srcdir_list'] = result
1774 def srcdir_duplicate(self, name):
1775 for dir in self.srcdir_list():
1776 if self.is_under(dir):
1777 # We shouldn't source from something in the build path;
1778 # variant_dir is probably under src_dir, in which case
1779 # we are reflecting.
1781 if dir.entry_exists_on_disk(name):
1782 srcnode = dir.Entry(name).disambiguate()
1784 node = self.Entry(name).disambiguate()
1785 node.do_duplicate(srcnode)
1791 def _srcdir_find_file_key(self, filename):
1794 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1796 def srcdir_find_file(self, filename):
1798 memo_dict = self._memo['srcdir_find_file']
1801 self._memo['srcdir_find_file'] = memo_dict
1804 return memo_dict[filename]
1809 if (isinstance(node, File) or isinstance(node, Entry)) and \
1810 (node.is_derived() or node.exists()):
1814 norm_name = _my_normcase(filename)
1816 for rdir in self.get_all_rdirs():
1817 try: node = rdir.entries[norm_name]
1818 except KeyError: node = rdir.file_on_disk(filename)
1819 else: node = func(node)
1821 result = (node, self)
1822 memo_dict[filename] = result
1825 for srcdir in self.srcdir_list():
1826 for rdir in srcdir.get_all_rdirs():
1827 try: node = rdir.entries[norm_name]
1828 except KeyError: node = rdir.file_on_disk(filename)
1829 else: node = func(node)
1831 result = (File(filename, self, self.fs), srcdir)
1832 memo_dict[filename] = result
1835 result = (None, None)
1836 memo_dict[filename] = result
1839 def dir_on_disk(self, name):
1840 if self.entry_exists_on_disk(name):
1841 try: return self.Dir(name)
1842 except TypeError: pass
1843 node = self.srcdir_duplicate(name)
1844 if isinstance(node, File):
1848 def file_on_disk(self, name):
1849 if self.entry_exists_on_disk(name) or \
1850 diskcheck_rcs(self, name) or \
1851 diskcheck_sccs(self, name):
1852 try: return self.File(name)
1853 except TypeError: pass
1854 node = self.srcdir_duplicate(name)
1855 if isinstance(node, Dir):
1859 def walk(self, func, arg):
1861 Walk this directory tree by calling the specified function
1862 for each directory in the tree.
1864 This behaves like the os.path.walk() function, but for in-memory
1865 Node.FS.Dir objects. The function takes the same arguments as
1866 the functions passed to os.path.walk():
1868 func(arg, dirname, fnames)
1870 Except that "dirname" will actually be the directory *Node*,
1871 not the string. The '.' and '..' entries are excluded from
1872 fnames. The fnames list may be modified in-place to filter the
1873 subdirectories visited or otherwise impose a specific order.
1874 The "arg" argument is always passed to func() and may be used
1875 in any way (or ignored, passing None is common).
1877 entries = self.entries
1878 names = entries.keys()
1881 func(arg, self, names)
1882 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1883 for dirname in filter(select_dirs, names):
1884 entries[dirname].walk(func, arg)
1886 def glob(self, pathname, ondisk=True, source=False, strings=False):
1888 Returns a list of Nodes (or strings) matching a specified
1891 Pathname patterns follow UNIX shell semantics: * matches
1892 any-length strings of any characters, ? matches any character,
1893 and [] can enclose lists or ranges of characters. Matches do
1894 not span directory separators.
1896 The matches take into account Repositories, returning local
1897 Nodes if a corresponding entry exists in a Repository (either
1898 an in-memory Node or something on disk).
1900 By defafult, the glob() function matches entries that exist
1901 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1902 argument to False (or some other non-true value) causes the glob()
1903 function to only match in-memory Nodes. The default behavior is
1904 to return both the on-disk and in-memory Nodes.
1906 The "source" argument, when true, specifies that corresponding
1907 source Nodes must be returned if you're globbing in a build
1908 directory (initialized with VariantDir()). The default behavior
1909 is to return Nodes local to the VariantDir().
1911 The "strings" argument, when true, returns the matches as strings,
1912 not Nodes. The strings are path names relative to this directory.
1914 The underlying algorithm is adapted from the glob.glob() function
1915 in the Python library (but heavily modified), and uses fnmatch()
1918 dirname, basename = os.path.split(pathname)
1920 return self._glob1(basename, ondisk, source, strings)
1921 if has_glob_magic(dirname):
1922 list = self.glob(dirname, ondisk, source, strings=False)
1924 list = [self.Dir(dirname, create=True)]
1927 r = dir._glob1(basename, ondisk, source, strings)
1929 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1931 result.sort(lambda a, b: cmp(str(a), str(b)))
1934 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1936 Globs for and returns a list of entry names matching a single
1937 pattern in this directory.
1939 This searches any repositories and source directories for
1940 corresponding entries and returns a Node (or string) relative
1941 to the current directory if an entry is found anywhere.
1943 TODO: handle pattern with no wildcard
1945 search_dir_list = self.get_all_rdirs()
1946 for srcdir in self.srcdir_list():
1947 search_dir_list.extend(srcdir.get_all_rdirs())
1949 selfEntry = self.Entry
1951 for dir in search_dir_list:
1952 # We use the .name attribute from the Node because the keys of
1953 # the dir.entries dictionary are normalized (that is, all upper
1954 # case) on case-insensitive systems like Windows.
1955 #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
1956 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1957 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1958 names.extend(node_names)
1960 # Make sure the working directory (self) actually has
1961 # entries for all Nodes in repositories or variant dirs.
1962 for name in node_names: selfEntry(name)
1965 disk_names = os.listdir(dir.abspath)
1968 names.extend(disk_names)
1970 # We're going to return corresponding Nodes in
1971 # the local directory, so we need to make sure
1972 # those Nodes exist. We only want to create
1973 # Nodes for the entries that will match the
1974 # specified pattern, though, which means we
1975 # need to filter the list here, even though
1976 # the overall list will also be filtered later,
1977 # after we exit this loop.
1978 if pattern[0] != '.':
1979 #disk_names = [ d for d in disk_names if d[0] != '.' ]
1980 disk_names = filter(lambda x: x[0] != '.', disk_names)
1981 disk_names = fnmatch.filter(disk_names, pattern)
1982 dirEntry = dir.Entry
1983 for name in disk_names:
1984 # Add './' before disk filename so that '#' at
1985 # beginning of filename isn't interpreted.
1987 node = dirEntry(name).disambiguate()
1989 if n.__class__ != node.__class__:
1990 n.__class__ = node.__class__
1994 if pattern[0] != '.':
1995 #names = [ n for n in names if n[0] != '.' ]
1996 names = filter(lambda x: x[0] != '.', names)
1997 names = fnmatch.filter(names, pattern)
2002 #return [ self.entries[_my_normcase(n)] for n in names ]
2003 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
2006 """A class for the root directory of a file system.
2008 This is the same as a Dir class, except that the path separator
2009 ('/' or '\\') is actually part of the name, so we don't need to
2010 add a separator when creating the path names of entries within
2013 def __init__(self, name, fs):
2014 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
2015 # We're going to be our own parent directory (".." entry and .dir
2016 # attribute) so we have to set up some values so Base.__init__()
2017 # won't gag won't it calls some of our methods.
2022 self.path_elements = []
2025 Base.__init__(self, name, self, fs)
2027 # Now set our paths to what we really want them to be: the
2028 # initial drive letter (the name) plus the directory separator,
2029 # except for the "lookup abspath," which does not have the
2031 self.abspath = name + os.sep
2033 self.path = name + os.sep
2034 self.tpath = name + os.sep
2037 self._lookupDict = {}
2039 # The // and os.sep + os.sep entries are necessary because
2040 # os.path.normpath() seems to preserve double slashes at the
2041 # beginning of a path (presumably for UNC path names), but
2042 # collapses triple slashes to a single slash.
2043 self._lookupDict[''] = self
2044 self._lookupDict['/'] = self
2045 self._lookupDict['//'] = self
2046 self._lookupDict[os.sep] = self
2047 self._lookupDict[os.sep + os.sep] = self
2049 def must_be_same(self, klass):
2052 Base.must_be_same(self, klass)
2054 def _lookup_abs(self, p, klass, create=1):
2056 Fast (?) lookup of a *normalized* absolute path.
2058 This method is intended for use by internal lookups with
2059 already-normalized path data. For general-purpose lookups,
2060 use the FS.Entry(), FS.Dir() or FS.File() methods.
2062 The caller is responsible for making sure we're passed a
2063 normalized absolute path; we merely let Python's dictionary look
2064 up and return the One True Node.FS object for the path.
2066 If no Node for the specified "p" doesn't already exist, and
2067 "create" is specified, the Node may be created after recursive
2068 invocation to find or create the parent directory or directories.
2072 result = self._lookupDict[k]
2075 raise SCons.Errors.UserError
2076 # There is no Node for this path name, and we're allowed
2078 dir_name, file_name = os.path.split(p)
2079 dir_node = self._lookup_abs(dir_name, Dir)
2080 result = klass(file_name, dir_node, self.fs)
2082 # Double-check on disk (as configured) that the Node we
2083 # created matches whatever is out there in the real world.
2084 result.diskcheck_match()
2086 self._lookupDict[k] = result
2087 dir_node.entries[_my_normcase(file_name)] = result
2088 dir_node.implicit = None
2090 # There is already a Node for this path name. Allow it to
2091 # complain if we were looking for an inappropriate type.
2092 result.must_be_same(klass)
2098 def entry_abspath(self, name):
2099 return self.abspath + name
2101 def entry_labspath(self, name):
2104 def entry_path(self, name):
2105 return self.path + name
2107 def entry_tpath(self, name):
2108 return self.tpath + name
2110 def is_under(self, dir):
2122 def src_builder(self):
2125 class FileNodeInfo(SCons.Node.NodeInfoBase):
2126 current_version_id = 1
2128 field_list = ['csig', 'timestamp', 'size']
2130 # This should get reset by the FS initialization.
2133 def str_to_node(self, s):
2137 drive, s = os.path.splitdrive(s)
2139 root = self.fs.get_root(drive)
2140 if not os.path.isabs(s):
2141 s = top.labspath + '/' + s
2142 return root._lookup_abs(s, Entry)
2144 class FileBuildInfo(SCons.Node.BuildInfoBase):
2145 current_version_id = 1
2147 def convert_to_sconsign(self):
2149 Converts this FileBuildInfo object for writing to a .sconsign file
2151 This replaces each Node in our various dependency lists with its
2152 usual string representation: relative to the top-level SConstruct
2153 directory, or an absolute path if it's outside.
2161 except AttributeError:
2164 s = string.replace(s, os.sep, '/')
2166 for attr in ['bsources', 'bdepends', 'bimplicit']:
2168 val = getattr(self, attr)
2169 except AttributeError:
2172 setattr(self, attr, map(node_to_str, val))
2173 def convert_from_sconsign(self, dir, name):
2175 Converts a newly-read FileBuildInfo object for in-SCons use
2177 For normal up-to-date checking, we don't have any conversion to
2178 perform--but we're leaving this method here to make that clear.
2181 def prepare_dependencies(self):
2183 Prepares a FileBuildInfo object for explaining what changed
2185 The bsources, bdepends and bimplicit lists have all been
2186 stored on disk as paths relative to the top-level SConstruct
2187 directory. Convert the strings to actual Nodes (for use by the
2188 --debug=explain code and --implicit-cache).
2191 ('bsources', 'bsourcesigs'),
2192 ('bdepends', 'bdependsigs'),
2193 ('bimplicit', 'bimplicitsigs'),
2195 for (nattr, sattr) in attrs:
2197 strings = getattr(self, nattr)
2198 nodeinfos = getattr(self, sattr)
2199 except AttributeError:
2202 for s, ni in izip(strings, nodeinfos):
2203 if not isinstance(s, SCons.Node.Node):
2204 s = ni.str_to_node(s)
2206 setattr(self, nattr, nodes)
2207 def format(self, names=0):
2209 bkids = self.bsources + self.bdepends + self.bimplicit
2210 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
2211 for bkid, bkidsig in izip(bkids, bkidsigs):
2212 result.append(str(bkid) + ': ' +
2213 string.join(bkidsig.format(names=names), ' '))
2214 result.append('%s [%s]' % (self.bactsig, self.bact))
2215 return string.join(result, '\n')
2218 """A class for files in a file system.
2221 memoizer_counters = []
2223 NodeInfo = FileNodeInfo
2224 BuildInfo = FileBuildInfo
2228 def diskcheck_match(self):
2229 diskcheck_match(self, self.isdir,
2230 "Directory %s found where file expected.")
2232 def __init__(self, name, directory, fs):
2233 if __debug__: logInstanceCreation(self, 'Node.FS.File')
2234 Base.__init__(self, name, directory, fs)
2237 def Entry(self, name):
2238 """Create an entry node named 'name' relative to
2239 the directory of this file."""
2240 return self.dir.Entry(name)
2242 def Dir(self, name, create=True):
2243 """Create a directory node named 'name' relative to
2244 the directory of this file."""
2245 return self.dir.Dir(name, create=create)
2247 def Dirs(self, pathlist):
2248 """Create a list of directories relative to the SConscript
2249 directory of this file."""
2251 # return [self.Dir(p) for p in pathlist]
2252 return map(lambda p, s=self: s.Dir(p), pathlist)
2254 def File(self, name):
2255 """Create a file node named 'name' relative to
2256 the directory of this file."""
2257 return self.dir.File(name)
2259 #def generate_build_dict(self):
2260 # """Return an appropriate dictionary of values for building
2262 # return {'Dir' : self.Dir,
2263 # 'File' : self.File,
2264 # 'RDirs' : self.RDirs}
2267 """Turn a file system node into a File object."""
2268 self.scanner_paths = {}
2269 if not hasattr(self, '_local'):
2272 # If there was already a Builder set on this entry, then
2273 # we need to make sure we call the target-decider function,
2274 # not the source-decider. Reaching in and doing this by hand
2275 # is a little bogus. We'd prefer to handle this by adding
2276 # an Entry.builder_set() method that disambiguates like the
2277 # other methods, but that starts running into problems with the
2278 # fragile way we initialize Dir Nodes with their Mkdir builders,
2279 # yet still allow them to be overridden by the user. Since it's
2280 # not clear right now how to fix that, stick with what works
2281 # until it becomes clear...
2282 if self.has_builder():
2283 self.changed_since_last_build = self.decide_target
2285 def scanner_key(self):
2286 return self.get_suffix()
2288 def get_contents(self):
2289 if not self.rexists():
2291 fname = self.rfile().abspath
2293 contents = open(fname, "rb").read()
2294 except EnvironmentError, e:
2303 get_text_contents = get_contents
2305 # This attempts to figure out what the encoding of the text is
2306 # based upon the BOM bytes, and then decodes the contents so that
2307 # it's a valid python string.
2308 def get_text_contents(self):
2309 contents = self.get_contents()
2310 if contents.startswith(codecs.BOM_UTF8):
2311 contents = contents.decode('utf-8')
2312 elif contents.startswith(codecs.BOM_UTF16):
2313 contents = contents.decode('utf-16')
2316 def get_content_hash(self):
2318 Compute and return the MD5 hash for this file.
2320 if not self.rexists():
2321 return SCons.Util.MD5signature('')
2322 fname = self.rfile().abspath
2324 cs = SCons.Util.MD5filesignature(fname,
2325 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2326 except EnvironmentError, e:
2333 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2337 return self._memo['get_size']
2342 size = self.rfile().getsize()
2346 self._memo['get_size'] = size
2350 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2352 def get_timestamp(self):
2354 return self._memo['get_timestamp']
2359 timestamp = self.rfile().getmtime()
2363 self._memo['get_timestamp'] = timestamp
2367 def store_info(self):
2368 # Merge our build information into the already-stored entry.
2369 # This accomodates "chained builds" where a file that's a target
2370 # in one build (SConstruct file) is a source in a different build.
2371 # See test/chained-build.py for the use case.
2373 self.dir.sconsign().store_info(self.name, self)
2375 convert_copy_attrs = [
2385 convert_sig_attrs = [
2391 def convert_old_entry(self, old_entry):
2392 # Convert a .sconsign entry from before the Big Signature
2393 # Refactoring, doing what we can to convert its information
2394 # to the new .sconsign entry format.
2396 # The old format looked essentially like this:
2405 # .bsourcesigs ("signature" list)
2407 # .bdependsigs ("signature" list)
2409 # .bimplicitsigs ("signature" list)
2413 # The new format looks like this:
2420 # .binfo (BuildInfo)
2422 # .bsourcesigs (NodeInfo list)
2428 # .bdependsigs (NodeInfo list)
2434 # .bimplicitsigs (NodeInfo list)
2442 # The basic idea of the new structure is that a NodeInfo always
2443 # holds all available information about the state of a given Node
2444 # at a certain point in time. The various .b*sigs lists can just
2445 # be a list of pointers to the .ninfo attributes of the different
2446 # dependent nodes, without any copying of information until it's
2447 # time to pickle it for writing out to a .sconsign file.
2449 # The complicating issue is that the *old* format only stored one
2450 # "signature" per dependency, based on however the *last* build
2451 # was configured. We don't know from just looking at it whether
2452 # it was a build signature, a content signature, or a timestamp
2453 # "signature". Since we no longer use build signatures, the
2454 # best we can do is look at the length and if it's thirty two,
2455 # assume that it was (or might have been) a content signature.
2456 # If it was actually a build signature, then it will cause a
2457 # rebuild anyway when it doesn't match the new content signature,
2458 # but that's probably the best we can do.
2459 import SCons.SConsign
2460 new_entry = SCons.SConsign.SConsignEntry()
2461 new_entry.binfo = self.new_binfo()
2462 binfo = new_entry.binfo
2463 for attr in self.convert_copy_attrs:
2465 value = getattr(old_entry, attr)
2466 except AttributeError:
2468 setattr(binfo, attr, value)
2469 delattr(old_entry, attr)
2470 for attr in self.convert_sig_attrs:
2472 sig_list = getattr(old_entry, attr)
2473 except AttributeError:
2476 for sig in sig_list:
2477 ninfo = self.new_ninfo()
2481 ninfo.timestamp = sig
2483 setattr(binfo, attr, value)
2484 delattr(old_entry, attr)
2487 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2489 def get_stored_info(self):
2491 return self._memo['get_stored_info']
2496 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2497 except (KeyError, EnvironmentError):
2498 import SCons.SConsign
2499 sconsign_entry = SCons.SConsign.SConsignEntry()
2500 sconsign_entry.binfo = self.new_binfo()
2501 sconsign_entry.ninfo = self.new_ninfo()
2503 if isinstance(sconsign_entry, FileBuildInfo):
2504 # This is a .sconsign file from before the Big Signature
2505 # Refactoring; convert it as best we can.
2506 sconsign_entry = self.convert_old_entry(sconsign_entry)
2508 delattr(sconsign_entry.ninfo, 'bsig')
2509 except AttributeError:
2512 self._memo['get_stored_info'] = sconsign_entry
2514 return sconsign_entry
2516 def get_stored_implicit(self):
2517 binfo = self.get_stored_info().binfo
2518 binfo.prepare_dependencies()
2519 try: return binfo.bimplicit
2520 except AttributeError: return None
2522 def rel_path(self, other):
2523 return self.dir.rel_path(other)
2525 def _get_found_includes_key(self, env, scanner, path):
2526 return (id(env), id(scanner), path)
2528 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2530 def get_found_includes(self, env, scanner, path):
2531 """Return the included implicit dependencies in this file.
2532 Cache results so we only scan the file once per path
2533 regardless of how many times this information is requested.
2535 memo_key = (id(env), id(scanner), path)
2537 memo_dict = self._memo['get_found_includes']
2540 self._memo['get_found_includes'] = memo_dict
2543 return memo_dict[memo_key]
2548 # result = [n.disambiguate() for n in scanner(self, env, path)]
2549 result = scanner(self, env, path)
2550 result = map(lambda N: N.disambiguate(), result)
2554 memo_dict[memo_key] = result
2558 def _createDir(self):
2559 # ensure that the directories for this node are
2563 def retrieve_from_cache(self):
2564 """Try to retrieve the node's content from a cache
2566 This method is called from multiple threads in a parallel build,
2567 so only do thread safe stuff here. Do thread unsafe stuff in
2570 Returns true iff the node was successfully retrieved.
2574 if not self.is_derived():
2576 return self.get_build_env().get_CacheDir().retrieve(self)
2580 Called just after this node is successfully built.
2582 # Push this file out to cache before the superclass Node.built()
2583 # method has a chance to clear the build signature, which it
2584 # will do if this file has a source scanner.
2586 # We have to clear the memoized values *before* we push it to
2587 # cache so that the memoization of the self.exists() return
2588 # value doesn't interfere.
2589 self.clear_memoized_values()
2591 self.get_build_env().get_CacheDir().push(self)
2592 SCons.Node.Node.built(self)
2596 self.get_build_env().get_CacheDir().push_if_forced(self)
2598 ninfo = self.get_ninfo()
2600 csig = self.get_max_drift_csig()
2604 ninfo.timestamp = self.get_timestamp()
2605 ninfo.size = self.get_size()
2607 if not self.has_builder():
2608 # This is a source file, but it might have been a target file
2609 # in another build that included more of the DAG. Copy
2610 # any build information that's stored in the .sconsign file
2611 # into our binfo object so it doesn't get lost.
2612 old = self.get_stored_info()
2613 self.get_binfo().__dict__.update(old.binfo.__dict__)
2617 def find_src_builder(self):
2620 scb = self.dir.src_builder()
2622 if diskcheck_sccs(self.dir, self.name):
2623 scb = get_DefaultSCCSBuilder()
2624 elif diskcheck_rcs(self.dir, self.name):
2625 scb = get_DefaultRCSBuilder()
2631 except AttributeError:
2634 self.builder_set(scb)
2637 def has_src_builder(self):
2638 """Return whether this Node has a source builder or not.
2640 If this Node doesn't have an explicit source code builder, this
2641 is where we figure out, on the fly, if there's a transparent
2642 source code builder for it.
2644 Note that if we found a source builder, we also set the
2645 self.builder attribute, so that all of the methods that actually
2646 *build* this file don't have to do anything different.
2650 except AttributeError:
2651 scb = self.sbuilder = self.find_src_builder()
2652 return scb is not None
2654 def alter_targets(self):
2655 """Return any corresponding targets in a variant directory.
2657 if self.is_derived():
2659 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2661 def _rmv_existing(self):
2662 self.clear_memoized_values()
2663 e = Unlink(self, [], None)
2664 if isinstance(e, SCons.Errors.BuildError):
2668 # Taskmaster interface subsystem
2671 def make_ready(self):
2672 self.has_src_builder()
2676 """Prepare for this file to be created."""
2677 SCons.Node.Node.prepare(self)
2679 if self.get_state() != SCons.Node.up_to_date:
2681 if self.is_derived() and not self.precious:
2682 self._rmv_existing()
2686 except SCons.Errors.StopError, drive:
2687 desc = "No drive `%s' for target `%s'." % (drive, self)
2688 raise SCons.Errors.StopError, desc
2695 """Remove this file."""
2696 if self.exists() or self.islink():
2697 self.fs.unlink(self.path)
2701 def do_duplicate(self, src):
2703 Unlink(self, None, None)
2704 e = Link(self, src, None)
2705 if isinstance(e, SCons.Errors.BuildError):
2706 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2707 raise SCons.Errors.StopError, desc
2709 # The Link() action may or may not have actually
2710 # created the file, depending on whether the -n
2711 # option was used or not. Delete the _exists and
2712 # _rexists attributes so they can be reevaluated.
2715 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2719 return self._memo['exists']
2722 # Duplicate from source path if we are set up to do this.
2723 if self.duplicate and not self.is_derived() and not self.linked:
2724 src = self.srcnode()
2726 # At this point, src is meant to be copied in a variant directory.
2728 if src.abspath != self.abspath:
2730 self.do_duplicate(src)
2731 # Can't return 1 here because the duplication might
2732 # not actually occur if the -n option is being used.
2734 # The source file does not exist. Make sure no old
2735 # copy remains in the variant directory.
2736 if Base.exists(self) or self.islink():
2737 self.fs.unlink(self.path)
2738 # Return None explicitly because the Base.exists() call
2739 # above will have cached its value if the file existed.
2740 self._memo['exists'] = None
2742 result = Base.exists(self)
2743 self._memo['exists'] = result
2747 # SIGNATURE SUBSYSTEM
2750 def get_max_drift_csig(self):
2752 Returns the content signature currently stored for this node
2753 if it's been unmodified longer than the max_drift value, or the
2754 max_drift value is 0. Returns None otherwise.
2756 old = self.get_stored_info()
2757 mtime = self.get_timestamp()
2759 max_drift = self.fs.max_drift
2761 if (time.time() - mtime) > max_drift:
2764 if n.timestamp and n.csig and n.timestamp == mtime:
2766 except AttributeError:
2768 elif max_drift == 0:
2770 return old.ninfo.csig
2771 except AttributeError:
2778 Generate a node's content signature, the digested signature
2782 cache - alternate node to use for the signature cache
2783 returns - the content signature
2785 ninfo = self.get_ninfo()
2788 except AttributeError:
2791 csig = self.get_max_drift_csig()
2795 if self.get_size() < SCons.Node.FS.File.md5_chunksize:
2796 contents = self.get_contents()
2798 csig = self.get_content_hash()
2800 # This can happen if there's actually a directory on-disk,
2801 # which can be the case if they've disabled disk checks,
2802 # or if an action with a File target actually happens to
2803 # create a same-named directory by mistake.
2807 csig = SCons.Util.MD5signature(contents)
2814 # DECISION SUBSYSTEM
2817 def builder_set(self, builder):
2818 SCons.Node.Node.builder_set(self, builder)
2819 self.changed_since_last_build = self.decide_target
2821 def changed_content(self, target, prev_ni):
2822 cur_csig = self.get_csig()
2824 return cur_csig != prev_ni.csig
2825 except AttributeError:
2828 def changed_state(self, target, prev_ni):
2829 return self.state != SCons.Node.up_to_date
2831 def changed_timestamp_then_content(self, target, prev_ni):
2832 if not self.changed_timestamp_match(target, prev_ni):
2834 self.get_ninfo().csig = prev_ni.csig
2835 except AttributeError:
2838 return self.changed_content(target, prev_ni)
2840 def changed_timestamp_newer(self, target, prev_ni):
2842 return self.get_timestamp() > target.get_timestamp()
2843 except AttributeError:
2846 def changed_timestamp_match(self, target, prev_ni):
2848 return self.get_timestamp() != prev_ni.timestamp
2849 except AttributeError:
2852 def decide_source(self, target, prev_ni):
2853 return target.get_build_env().decide_source(self, target, prev_ni)
2855 def decide_target(self, target, prev_ni):
2856 return target.get_build_env().decide_target(self, target, prev_ni)
2858 # Initialize this Node's decider function to decide_source() because
2859 # every file is a source file until it has a Builder attached...
2860 changed_since_last_build = decide_source
2862 def is_up_to_date(self):
2864 if T: Trace('is_up_to_date(%s):' % self)
2865 if not self.exists():
2866 if T: Trace(' not self.exists():')
2867 # The file doesn't exist locally...
2870 # ...but there is one in a Repository...
2871 if not self.changed(r):
2872 if T: Trace(' changed(%s):' % r)
2873 # ...and it's even up-to-date...
2875 # ...and they'd like a local copy.
2876 e = LocalCopy(self, r, None)
2877 if isinstance(e, SCons.Errors.BuildError):
2883 if T: Trace(' None\n')
2887 if T: Trace(' self.exists(): %s\n' % r)
2890 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2894 return self._memo['rfile']
2898 if not self.exists():
2899 norm_name = _my_normcase(self.name)
2900 for dir in self.dir.get_all_rdirs():
2901 try: node = dir.entries[norm_name]
2902 except KeyError: node = dir.file_on_disk(self.name)
2903 if node and node.exists() and \
2904 (isinstance(node, File) or isinstance(node, Entry) \
2905 or not node.is_derived()):
2907 # Copy over our local attributes to the repository
2908 # Node so we identify shared object files in the
2909 # repository and don't assume they're static.
2911 # This isn't perfect; the attribute would ideally
2912 # be attached to the object in the repository in
2913 # case it was built statically in the repository
2914 # and we changed it to shared locally, but that's
2915 # rarely the case and would only occur if you
2916 # intentionally used the same suffix for both
2917 # shared and static objects anyway. So this
2918 # should work well in practice.
2919 result.attributes = self.attributes
2921 self._memo['rfile'] = result
2925 return str(self.rfile())
2927 def get_cachedir_csig(self):
2929 Fetch a Node's content signature for purposes of computing
2930 another Node's cachesig.
2932 This is a wrapper around the normal get_csig() method that handles
2933 the somewhat obscure case of using CacheDir with the -n option.
2934 Any files that don't exist would normally be "built" by fetching
2935 them from the cache, but the normal get_csig() method will try
2936 to open up the local file, which doesn't exist because the -n
2937 option meant we didn't actually pull the file from cachedir.
2938 But since the file *does* actually exist in the cachedir, we
2939 can use its contents for the csig.
2942 return self.cachedir_csig
2943 except AttributeError:
2946 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2947 if not self.exists() and cachefile and os.path.exists(cachefile):
2948 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
2949 SCons.Node.FS.File.md5_chunksize * 1024)
2951 self.cachedir_csig = self.get_csig()
2952 return self.cachedir_csig
2954 def get_cachedir_bsig(self):
2956 return self.cachesig
2957 except AttributeError:
2960 # Add the path to the cache signature, because multiple
2961 # targets built by the same action will all have the same
2962 # build signature, and we have to differentiate them somehow.
2963 children = self.children()
2964 executor = self.get_executor()
2965 # sigs = [n.get_cachedir_csig() for n in children]
2966 sigs = map(lambda n: n.get_cachedir_csig(), children)
2967 sigs.append(SCons.Util.MD5signature(executor.get_contents()))
2968 sigs.append(self.path)
2969 result = self.cachesig = SCons.Util.MD5collect(sigs)
2975 def get_default_fs():
2984 if SCons.Memoize.use_memoizer:
2985 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2987 memoizer_counters = []
2992 def filedir_lookup(self, p, fd=None):
2994 A helper method for find_file() that looks up a directory for
2995 a file we're trying to find. This only creates the Dir Node if
2996 it exists on-disk, since if the directory doesn't exist we know
2997 we won't find any files in it... :-)
2999 It would be more compact to just use this as a nested function
3000 with a default keyword argument (see the commented-out version
3001 below), but that doesn't work unless you have nested scopes,
3002 so we define it here just so this work under Python 1.5.2.
3005 fd = self.default_filedir
3006 dir, name = os.path.split(fd)
3007 drive, d = os.path.splitdrive(dir)
3008 if d in ('/', os.sep):
3009 return p.fs.get_root(drive).dir_on_disk(name)
3011 p = self.filedir_lookup(p, dir)
3014 norm_name = _my_normcase(name)
3016 node = p.entries[norm_name]
3018 return p.dir_on_disk(name)
3019 if isinstance(node, Dir):
3021 if isinstance(node, Entry):
3022 node.must_be_same(Dir)
3026 def _find_file_key(self, filename, paths, verbose=None):
3027 return (filename, paths)
3029 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
3031 def find_file(self, filename, paths, verbose=None):
3033 find_file(str, [Dir()]) -> [nodes]
3035 filename - a filename to find
3036 paths - a list of directory path *nodes* to search in. Can be
3037 represented as a list, a tuple, or a callable that is
3038 called with no arguments and returns the list or tuple.
3040 returns - the node created from the found file.
3042 Find a node corresponding to either a derived file or a file
3043 that exists already.
3045 Only the first file found is returned, and none is returned
3046 if no file is found.
3048 memo_key = self._find_file_key(filename, paths)
3050 memo_dict = self._memo['find_file']
3053 self._memo['find_file'] = memo_dict
3056 return memo_dict[memo_key]
3060 if verbose and not callable(verbose):
3061 if not SCons.Util.is_String(verbose):
3062 verbose = "find_file"
3063 verbose = ' %s: ' % verbose
3064 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
3066 filedir, filename = os.path.split(filename)
3068 # More compact code that we can't use until we drop
3069 # support for Python 1.5.2:
3071 #def filedir_lookup(p, fd=filedir):
3073 # A helper function that looks up a directory for a file
3074 # we're trying to find. This only creates the Dir Node
3075 # if it exists on-disk, since if the directory doesn't
3076 # exist we know we won't find any files in it... :-)
3078 # dir, name = os.path.split(fd)
3080 # p = filedir_lookup(p, dir)
3083 # norm_name = _my_normcase(name)
3085 # node = p.entries[norm_name]
3087 # return p.dir_on_disk(name)
3088 # if isinstance(node, Dir):
3090 # if isinstance(node, Entry):
3091 # node.must_be_same(Dir)
3093 # if isinstance(node, Dir) or isinstance(node, Entry):
3096 #paths = filter(None, map(filedir_lookup, paths))
3098 self.default_filedir = filedir
3099 paths = filter(None, map(self.filedir_lookup, paths))
3104 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3105 node, d = dir.srcdir_find_file(filename)
3108 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3112 memo_dict[memo_key] = result
3116 find_file = FileFinder().find_file
3119 def invalidate_node_memos(targets):
3121 Invalidate the memoized values of all Nodes (files or directories)
3122 that are associated with the given entries. Has been added to
3123 clear the cache of nodes affected by a direct execution of an
3124 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3125 inconsistent if the action is run through Execute(). The argument
3126 `targets` can be a single Node object or filename, or a sequence
3129 from traceback import extract_stack
3131 # First check if the cache really needs to be flushed. Only
3132 # actions run in the SConscript with Execute() seem to be
3133 # affected. XXX The way to check if Execute() is in the stacktrace
3134 # is a very dirty hack and should be replaced by a more sensible
3136 for f in extract_stack():
3137 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3140 # Dont have to invalidate, so return
3143 if not SCons.Util.is_List(targets):
3146 for entry in targets:
3147 # If the target is a Node object, clear the cache. If it is a
3148 # filename, look up potentially existing Node object first.
3150 entry.clear_memoized_values()
3151 except AttributeError:
3152 # Not a Node object, try to look up Node by filename. XXX
3153 # This creates Node objects even for those filenames which
3154 # do not correspond to an existing Node object.
3155 node = get_default_fs().Entry(entry)
3157 node.clear_memoized_values()
3161 # indent-tabs-mode:nil
3163 # vim: set expandtab tabstop=4 shiftwidth=4: