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)
555 # Filenames and paths are probably reused and are intern'ed to
557 self.name = intern(name)
558 self.suffix = intern(SCons.Util.splitext(name)[1])
561 assert directory, "A directory must be provided"
563 self.abspath = intern(directory.entry_abspath(name))
564 self.labspath = intern(directory.entry_labspath(name))
565 if directory.path == '.':
566 self.path = intern(name)
568 self.path = intern(directory.entry_path(name))
569 if directory.tpath == '.':
570 self.tpath = intern(name)
572 self.tpath = intern(directory.entry_tpath(name))
573 self.path_elements = directory.path_elements + [self]
576 self.cwd = None # will hold the SConscript directory for target nodes
577 self.duplicate = directory.duplicate
579 def str_for_display(self):
580 return '"' + self.__str__() + '"'
582 def must_be_same(self, klass):
584 This node, which already existed, is being looked up as the
585 specified klass. Raise an exception if it isn't.
587 if isinstance(self, klass) or klass is Entry:
589 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
590 (self.__class__.__name__, self.path, klass.__name__)
595 def get_suffix(self):
602 """A Node.FS.Base object's string representation is its path
606 return self._save_str()
607 return self._get_str()
609 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
613 return self._memo['_save_str']
616 result = intern(self._get_str())
617 self._memo['_save_str'] = result
622 if self.duplicate or self.is_derived():
623 return self.get_path()
624 srcnode = self.srcnode()
625 if srcnode.stat() is None and self.stat() is not None:
626 result = self.get_path()
628 result = srcnode.get_path()
630 # We're not at the point where we're saving the string string
631 # representations of FS Nodes (because we haven't finished
632 # reading the SConscript files and need to have str() return
633 # things relative to them). That also means we can't yet
634 # cache values returned (or not returned) by stat(), since
635 # Python code in the SConscript files might still create
636 # or otherwise affect the on-disk file. So get rid of the
637 # values that the underlying stat() method saved.
638 try: del self._memo['stat']
639 except KeyError: pass
640 if self is not srcnode:
641 try: del srcnode._memo['stat']
642 except KeyError: pass
647 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
650 try: return self._memo['stat']
651 except KeyError: pass
652 try: result = self.fs.stat(self.abspath)
653 except os.error: result = None
654 self._memo['stat'] = result
658 return self.stat() is not None
661 return self.rfile().exists()
665 if st: return st[stat.ST_MTIME]
670 if st: return st[stat.ST_SIZE]
675 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
679 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
681 if hasattr(os, 'symlink'):
683 try: st = self.fs.lstat(self.abspath)
684 except os.error: return 0
685 return stat.S_ISLNK(st[stat.ST_MODE])
688 return 0 # no symlinks
690 def is_under(self, dir):
694 return self.dir.is_under(dir)
700 """If this node is in a build path, return the node
701 corresponding to its source file. Otherwise, return
704 srcdir_list = self.dir.srcdir_list()
706 srcnode = srcdir_list[0].Entry(self.name)
707 srcnode.must_be_same(self.__class__)
711 def get_path(self, dir=None):
712 """Return path relative to the current working directory of the
713 Node.FS.Base object that owns us."""
715 dir = self.fs.getcwd()
718 path_elems = self.path_elements
719 try: i = path_elems.index(dir)
720 except ValueError: pass
721 else: path_elems = path_elems[i+1:]
722 path_elems = map(lambda n: n.name, path_elems)
723 return string.join(path_elems, os.sep)
725 def set_src_builder(self, builder):
726 """Set the source code builder for this node."""
727 self.sbuilder = builder
728 if not self.has_builder():
729 self.builder_set(builder)
731 def src_builder(self):
732 """Fetch the source code builder for this node.
734 If there isn't one, we cache the source code builder specified
735 for the directory (which in turn will cache the value from its
736 parent directory, and so on up to the file system root).
740 except AttributeError:
741 scb = self.dir.src_builder()
745 def get_abspath(self):
746 """Get the absolute path of the file."""
749 def for_signature(self):
750 # Return just our name. Even an absolute path would not work,
751 # because that can change thanks to symlinks or remapped network
755 def get_subst_proxy(self):
758 except AttributeError:
759 ret = EntryProxy(self)
763 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
766 Generates a target entry that corresponds to this entry (usually
767 a source file) with the specified prefix and suffix.
769 Note that this method can be overridden dynamically for generated
770 files that need different behavior. See Tool/swig.py for
773 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
775 def _Rfindalldirs_key(self, pathlist):
778 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
780 def Rfindalldirs(self, pathlist):
782 Return all of the directories for a given path list, including
783 corresponding "backing" directories in any repositories.
785 The Node lookups are relative to this Node (typically a
786 directory), so memoizing result saves cycles from looking
787 up the same path for each target in a given directory.
790 memo_dict = self._memo['Rfindalldirs']
793 self._memo['Rfindalldirs'] = memo_dict
796 return memo_dict[pathlist]
800 create_dir_relative_to_self = self.Dir
802 for path in pathlist:
803 if isinstance(path, SCons.Node.Node):
806 dir = create_dir_relative_to_self(path)
807 result.extend(dir.get_all_rdirs())
809 memo_dict[pathlist] = result
813 def RDirs(self, pathlist):
814 """Search for a list of directories in the Repository list."""
815 cwd = self.cwd or self.fs._cwd
816 return cwd.Rfindalldirs(pathlist)
818 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
822 return self._memo['rentry']
826 if not self.exists():
827 norm_name = _my_normcase(self.name)
828 for dir in self.dir.get_all_rdirs():
830 node = dir.entries[norm_name]
832 if dir.entry_exists_on_disk(self.name):
833 result = dir.Entry(self.name)
835 self._memo['rentry'] = result
838 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
842 """This is the class for generic Node.FS entries--that is, things
843 that could be a File or a Dir, but we're just not sure yet.
844 Consequently, the methods in this class really exist just to
845 transform their associated object into the right class when the
846 time comes, and then call the same-named method in the transformed
849 def diskcheck_match(self):
852 def disambiguate(self, must_exist=None):
859 self.__class__ = File
863 # There was nothing on-disk at this location, so look in
866 # We can't just use self.srcnode() straight away because
867 # that would create an actual Node for this file in the src
868 # directory, and there might not be one. Instead, use the
869 # dir_on_disk() method to see if there's something on-disk
870 # with that name, in which case we can go ahead and call
871 # self.srcnode() to create the right type of entry.
872 srcdir = self.dir.srcnode()
873 if srcdir != self.dir and \
874 srcdir.entry_exists_on_disk(self.name) and \
875 self.srcnode().isdir():
879 msg = "No such file or directory: '%s'" % self.abspath
880 raise SCons.Errors.UserError, msg
882 self.__class__ = File
888 """We're a generic Entry, but the caller is actually looking for
889 a File at this point, so morph into one."""
890 self.__class__ = File
893 return File.rfile(self)
895 def scanner_key(self):
896 return self.get_suffix()
898 def get_contents(self):
899 """Fetch the contents of the entry. Returns the exact binary
900 contents of the file."""
902 self = self.disambiguate(must_exist=1)
903 except SCons.Errors.UserError:
904 # There was nothing on disk with which to disambiguate
905 # this entry. Leave it as an Entry, but return a null
906 # string so calls to get_contents() in emitters and the
907 # like (e.g. in qt.py) don't have to disambiguate by hand
908 # or catch the exception.
911 return self.get_contents()
913 def get_text_contents(self):
914 """Fetch the decoded text contents of a Unicode encoded Entry.
916 Since this should return the text contents from the file
917 system, we check to see into what sort of subclass we should
920 self = self.disambiguate(must_exist=1)
921 except SCons.Errors.UserError:
922 # There was nothing on disk with which to disambiguate
923 # this entry. Leave it as an Entry, but return a null
924 # string so calls to get_text_contents() in emitters and
925 # the like (e.g. in qt.py) don't have to disambiguate by
926 # hand or catch the exception.
929 return self.get_text_contents()
931 def must_be_same(self, klass):
932 """Called to make sure a Node is a Dir. Since we're an
933 Entry, we can morph into one."""
934 if self.__class__ is not klass:
935 self.__class__ = klass
939 # The following methods can get called before the Taskmaster has
940 # had a chance to call disambiguate() directly to see if this Entry
941 # should really be a Dir or a File. We therefore use these to call
942 # disambiguate() transparently (from our caller's point of view).
944 # Right now, this minimal set of methods has been derived by just
945 # looking at some of the methods that will obviously be called early
946 # in any of the various Taskmasters' calling sequences, and then
947 # empirically figuring out which additional methods are necessary
948 # to make various tests pass.
951 """Return if the Entry exists. Check the file system to see
952 what we should turn into first. Assume a file if there's no
954 return self.disambiguate().exists()
956 def rel_path(self, other):
957 d = self.disambiguate()
958 if d.__class__ is Entry:
959 raise "rel_path() could not disambiguate File/Dir"
960 return d.rel_path(other)
963 return self.disambiguate().new_ninfo()
965 def changed_since_last_build(self, target, prev_ni):
966 return self.disambiguate().changed_since_last_build(target, prev_ni)
968 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
969 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
971 def get_subst_proxy(self):
972 return self.disambiguate().get_subst_proxy()
974 # This is for later so we can differentiate between Entry the class and Entry
975 # the method of the FS class.
981 if SCons.Memoize.use_memoizer:
982 __metaclass__ = SCons.Memoize.Memoized_Metaclass
984 # This class implements an abstraction layer for operations involving
985 # a local file system. Essentially, this wraps any function in
986 # the os, os.path or shutil modules that we use to actually go do
987 # anything with or to the local file system.
989 # Note that there's a very good chance we'll refactor this part of
990 # the architecture in some way as we really implement the interface(s)
991 # for remote file system Nodes. For example, the right architecture
992 # might be to have this be a subclass instead of a base class.
993 # Nevertheless, we're using this as a first step in that direction.
995 # We're not using chdir() yet because the calling subclass method
996 # needs to use os.chdir() directly to avoid recursion. Will we
997 # really need this one?
998 #def chdir(self, path):
999 # return os.chdir(path)
1000 def chmod(self, path, mode):
1001 return os.chmod(path, mode)
1002 def copy(self, src, dst):
1003 return shutil.copy(src, dst)
1004 def copy2(self, src, dst):
1005 return shutil.copy2(src, dst)
1006 def exists(self, path):
1007 return os.path.exists(path)
1008 def getmtime(self, path):
1009 return os.path.getmtime(path)
1010 def getsize(self, path):
1011 return os.path.getsize(path)
1012 def isdir(self, path):
1013 return os.path.isdir(path)
1014 def isfile(self, path):
1015 return os.path.isfile(path)
1016 def link(self, src, dst):
1017 return os.link(src, dst)
1018 def lstat(self, path):
1019 return os.lstat(path)
1020 def listdir(self, path):
1021 return os.listdir(path)
1022 def makedirs(self, path):
1023 return os.makedirs(path)
1024 def mkdir(self, path):
1025 return os.mkdir(path)
1026 def rename(self, old, new):
1027 return os.rename(old, new)
1028 def stat(self, path):
1029 return os.stat(path)
1030 def symlink(self, src, dst):
1031 return os.symlink(src, dst)
1032 def open(self, path):
1034 def unlink(self, path):
1035 return os.unlink(path)
1037 if hasattr(os, 'symlink'):
1038 def islink(self, path):
1039 return os.path.islink(path)
1041 def islink(self, path):
1042 return 0 # no symlinks
1044 if hasattr(os, 'readlink'):
1045 def readlink(self, file):
1046 return os.readlink(file)
1048 def readlink(self, file):
1053 # # Skeleton for the obvious methods we might need from the
1054 # # abstraction layer for a remote filesystem.
1055 # def upload(self, local_src, remote_dst):
1057 # def download(self, remote_src, local_dst):
1063 memoizer_counters = []
1065 def __init__(self, path = None):
1066 """Initialize the Node.FS subsystem.
1068 The supplied path is the top of the source tree, where we
1069 expect to find the top-level build file. If no path is
1070 supplied, the current directory is the default.
1072 The path argument must be a valid absolute path.
1074 if __debug__: logInstanceCreation(self, 'Node.FS')
1079 self.SConstruct_dir = None
1080 self.max_drift = default_max_drift
1084 self.pathTop = os.getcwd()
1087 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1089 self.Top = self.Dir(self.pathTop)
1091 self.Top.tpath = '.'
1092 self._cwd = self.Top
1094 DirNodeInfo.fs = self
1095 FileNodeInfo.fs = self
1097 def set_SConstruct_dir(self, dir):
1098 self.SConstruct_dir = dir
1100 def get_max_drift(self):
1101 return self.max_drift
1103 def set_max_drift(self, max_drift):
1104 self.max_drift = max_drift
1109 def chdir(self, dir, change_os_dir=0):
1110 """Change the current working directory for lookups.
1111 If change_os_dir is true, we will also change the "real" cwd
1119 os.chdir(dir.abspath)
1124 def get_root(self, drive):
1126 Returns the root directory for the specified drive, creating
1129 drive = _my_normcase(drive)
1131 return self.Root[drive]
1133 root = RootDir(drive, self)
1134 self.Root[drive] = root
1136 self.Root[self.defaultDrive] = root
1137 elif drive == self.defaultDrive:
1138 self.Root[''] = root
1141 def _lookup(self, p, directory, fsclass, create=1):
1143 The generic entry point for Node lookup with user-supplied data.
1145 This translates arbitrary input into a canonical Node.FS object
1146 of the specified fsclass. The general approach for strings is
1147 to turn it into a fully normalized absolute path and then call
1148 the root directory's lookup_abs() method for the heavy lifting.
1150 If the path name begins with '#', it is unconditionally
1151 interpreted relative to the top-level directory of this FS. '#'
1152 is treated as a synonym for the top-level SConstruct directory,
1153 much like '~' is treated as a synonym for the user's home
1154 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1155 to the 'foo' subdirectory underneath the top-level SConstruct
1158 If the path name is relative, then the path is looked up relative
1159 to the specified directory, or the current directory (self._cwd,
1160 typically the SConscript directory) if the specified directory
1163 if isinstance(p, Base):
1164 # It's already a Node.FS object. Make sure it's the right
1166 p.must_be_same(fsclass)
1168 # str(p) in case it's something like a proxy object
1171 initial_hash = (p[0:1] == '#')
1173 # There was an initial '#', so we strip it and override
1174 # whatever directory they may have specified with the
1175 # top-level SConstruct directory.
1177 directory = self.Top
1179 if directory and not isinstance(directory, Dir):
1180 directory = self.Dir(directory)
1183 drive, p = os.path.splitdrive(p)
1187 # This causes a naked drive letter to be treated as a synonym
1188 # for the root directory on that drive.
1190 absolute = os.path.isabs(p)
1192 needs_normpath = needs_normpath_check.match(p)
1194 if initial_hash or not absolute:
1195 # This is a relative lookup, either to the top-level
1196 # SConstruct directory (because of the initial '#') or to
1197 # the current directory (the path name is not absolute).
1198 # Add the string to the appropriate directory lookup path,
1199 # after which the whole thing gets normalized.
1201 directory = self._cwd
1203 p = directory.labspath + '/' + p
1205 p = directory.labspath
1208 p = os.path.normpath(p)
1210 if drive or absolute:
1211 root = self.get_root(drive)
1214 directory = self._cwd
1215 root = directory.root
1218 p = string.replace(p, os.sep, '/')
1219 return root._lookup_abs(p, fsclass, create)
1221 def Entry(self, name, directory = None, create = 1):
1222 """Look up or create a generic Entry node with the specified name.
1223 If the name is a relative path (begins with ./, ../, or a file
1224 name), then it is looked up relative to the supplied directory
1225 node, or to the top level directory of the FS (supplied at
1226 construction time) if no directory is supplied.
1228 return self._lookup(name, directory, Entry, create)
1230 def File(self, name, directory = None, create = 1):
1231 """Look up or create a File node with the specified name. If
1232 the name is a relative path (begins with ./, ../, or a file name),
1233 then it is looked up relative to the supplied directory node,
1234 or to the top level directory of the FS (supplied at construction
1235 time) if no directory is supplied.
1237 This method will raise TypeError if a directory is found at the
1240 return self._lookup(name, directory, File, create)
1242 def Dir(self, name, directory = None, create = True):
1243 """Look up or create a Dir node with the specified name. If
1244 the name is a relative path (begins with ./, ../, or a file name),
1245 then it is looked up relative to the supplied directory node,
1246 or to the top level directory of the FS (supplied at construction
1247 time) if no directory is supplied.
1249 This method will raise TypeError if a normal file is found at the
1252 return self._lookup(name, directory, Dir, create)
1254 def VariantDir(self, variant_dir, src_dir, duplicate=1):
1255 """Link the supplied variant directory to the source directory
1256 for purposes of building files."""
1258 if not isinstance(src_dir, SCons.Node.Node):
1259 src_dir = self.Dir(src_dir)
1260 if not isinstance(variant_dir, SCons.Node.Node):
1261 variant_dir = self.Dir(variant_dir)
1262 if src_dir.is_under(variant_dir):
1263 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1264 if variant_dir.srcdir:
1265 if variant_dir.srcdir == src_dir:
1266 return # We already did this.
1267 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1268 variant_dir.link(src_dir, duplicate)
1270 def Repository(self, *dirs):
1271 """Specify Repository directories to search."""
1273 if not isinstance(d, SCons.Node.Node):
1275 self.Top.addRepository(d)
1277 def variant_dir_target_climb(self, orig, dir, tail):
1278 """Create targets in corresponding variant directories
1280 Climb the directory tree, and look up path names
1281 relative to any linked variant directories we find.
1283 Even though this loops and walks up the tree, we don't memoize
1284 the return value because this is really only used to process
1285 the command-line targets.
1289 fmt = "building associated VariantDir targets: %s"
1292 for bd in dir.variant_dirs:
1293 if start_dir.is_under(bd):
1294 # If already in the build-dir location, don't reflect
1295 return [orig], fmt % str(orig)
1296 p = apply(os.path.join, [bd.path] + tail)
1297 targets.append(self.Entry(p))
1298 tail = [dir.name] + tail
1301 message = fmt % string.join(map(str, targets))
1302 return targets, message
1304 def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1308 This is mainly a shim layer
1312 return cwd.glob(pathname, ondisk, source, strings)
1314 class DirNodeInfo(SCons.Node.NodeInfoBase):
1315 # This should get reset by the FS initialization.
1316 current_version_id = 1
1320 def str_to_node(self, s):
1324 drive, s = os.path.splitdrive(s)
1326 root = self.fs.get_root(drive)
1327 if not os.path.isabs(s):
1328 s = top.labspath + '/' + s
1329 return root._lookup_abs(s, Entry)
1331 class DirBuildInfo(SCons.Node.BuildInfoBase):
1332 current_version_id = 1
1334 glob_magic_check = re.compile('[*?[]')
1336 def has_glob_magic(s):
1337 return glob_magic_check.search(s) is not None
1340 """A class for directories in a file system.
1343 memoizer_counters = []
1345 NodeInfo = DirNodeInfo
1346 BuildInfo = DirBuildInfo
1348 def __init__(self, name, directory, fs):
1349 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1350 Base.__init__(self, name, directory, fs)
1354 """Turn a file system Node (either a freshly initialized directory
1355 object or a separate Entry object) into a proper directory object.
1357 Set up this directory's entries and hook it into the file
1358 system tree. Specify that directories (this Node) don't use
1359 signatures for calculating whether they're current.
1362 self.repositories = []
1366 self.entries['.'] = self
1367 self.entries['..'] = self.dir
1370 self._sconsign = None
1371 self.variant_dirs = []
1372 self.root = self.dir.root
1374 # Don't just reset the executor, replace its action list,
1375 # because it might have some pre-or post-actions that need to
1377 self.builder = get_MkdirBuilder()
1378 self.get_executor().set_action_list(self.builder.action)
1380 def diskcheck_match(self):
1381 diskcheck_match(self, self.isfile,
1382 "File %s found where directory expected.")
1384 def __clearRepositoryCache(self, duplicate=None):
1385 """Called when we change the repository(ies) for a directory.
1386 This clears any cached information that is invalidated by changing
1389 for node in self.entries.values():
1390 if node != self.dir:
1391 if node != self and isinstance(node, Dir):
1392 node.__clearRepositoryCache(duplicate)
1397 except AttributeError:
1399 if duplicate is not None:
1400 node.duplicate=duplicate
1402 def __resetDuplicate(self, node):
1404 node.duplicate = node.get_dir().duplicate
1406 def Entry(self, name):
1408 Looks up or creates an entry node named 'name' relative to
1411 return self.fs.Entry(name, self)
1413 def Dir(self, name, create=True):
1415 Looks up or creates a directory node named 'name' relative to
1418 return self.fs.Dir(name, self, create)
1420 def File(self, name):
1422 Looks up or creates a file node named 'name' relative to
1425 return self.fs.File(name, self)
1427 def _lookup_rel(self, name, klass, create=1):
1429 Looks up a *normalized* relative path name, relative to this
1432 This method is intended for use by internal lookups with
1433 already-normalized path data. For general-purpose lookups,
1434 use the Entry(), Dir() and File() methods above.
1436 This method does *no* input checking and will die or give
1437 incorrect results if it's passed a non-normalized path name (e.g.,
1438 a path containing '..'), an absolute path name, a top-relative
1439 ('#foo') path name, or any kind of object.
1441 name = self.entry_labspath(name)
1442 return self.root._lookup_abs(name, klass, create)
1444 def link(self, srcdir, duplicate):
1445 """Set this directory as the variant directory for the
1446 supplied source directory."""
1447 self.srcdir = srcdir
1448 self.duplicate = duplicate
1449 self.__clearRepositoryCache(duplicate)
1450 srcdir.variant_dirs.append(self)
1452 def getRepositories(self):
1453 """Returns a list of repositories for this directory.
1455 if self.srcdir and not self.duplicate:
1456 return self.srcdir.get_all_rdirs() + self.repositories
1457 return self.repositories
1459 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1461 def get_all_rdirs(self):
1463 return list(self._memo['get_all_rdirs'])
1471 for rep in dir.getRepositories():
1472 result.append(rep.Dir(fname))
1476 fname = dir.name + os.sep + fname
1479 self._memo['get_all_rdirs'] = list(result)
1483 def addRepository(self, dir):
1484 if dir != self and not dir in self.repositories:
1485 self.repositories.append(dir)
1487 self.__clearRepositoryCache()
1490 return self.entries['..']
1492 def _rel_path_key(self, other):
1495 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1497 def rel_path(self, other):
1498 """Return a path to "other" relative to this directory.
1501 # This complicated and expensive method, which constructs relative
1502 # paths between arbitrary Node.FS objects, is no longer used
1503 # by SCons itself. It was introduced to store dependency paths
1504 # in .sconsign files relative to the target, but that ended up
1505 # being significantly inefficient.
1507 # We're continuing to support the method because some SConstruct
1508 # files out there started using it when it was available, and
1509 # we're all about backwards compatibility..
1512 memo_dict = self._memo['rel_path']
1515 self._memo['rel_path'] = memo_dict
1518 return memo_dict[other]
1525 elif not other in self.path_elements:
1527 other_dir = other.get_dir()
1528 except AttributeError:
1531 if other_dir is None:
1534 dir_rel_path = self.rel_path(other_dir)
1535 if dir_rel_path == '.':
1538 result = dir_rel_path + os.sep + other.name
1540 i = self.path_elements.index(other) + 1
1542 path_elems = ['..'] * (len(self.path_elements) - i) \
1543 + map(lambda n: n.name, other.path_elements[i:])
1545 result = string.join(path_elems, os.sep)
1547 memo_dict[other] = result
1551 def get_env_scanner(self, env, kw={}):
1552 import SCons.Defaults
1553 return SCons.Defaults.DirEntryScanner
1555 def get_target_scanner(self):
1556 import SCons.Defaults
1557 return SCons.Defaults.DirEntryScanner
1559 def get_found_includes(self, env, scanner, path):
1560 """Return this directory's implicit dependencies.
1562 We don't bother caching the results because the scan typically
1563 shouldn't be requested more than once (as opposed to scanning
1564 .h file contents, which can be requested as many times as the
1565 files is #included by other files).
1569 # Clear cached info for this Dir. If we already visited this
1570 # directory on our walk down the tree (because we didn't know at
1571 # that point it was being used as the source for another Node)
1572 # then we may have calculated build signature before realizing
1573 # we had to scan the disk. Now that we have to, though, we need
1574 # to invalidate the old calculated signature so that any node
1575 # dependent on our directory structure gets one that includes
1576 # info about everything on disk.
1578 return scanner(self, env, path)
1581 # Taskmaster interface subsystem
1587 def build(self, **kw):
1588 """A null "builder" for directories."""
1590 if self.builder is not MkdirBuilder:
1591 apply(SCons.Node.Node.build, [self,], kw)
1598 """Create this directory, silently and without worrying about
1599 whether the builder is the default or not."""
1605 listDirs.append(parent)
1608 # Don't use while: - else: for this condition because
1609 # if so, then parent is None and has no .path attribute.
1610 raise SCons.Errors.StopError, parent.path
1613 for dirnode in listDirs:
1615 # Don't call dirnode.build(), call the base Node method
1616 # directly because we definitely *must* create this
1617 # directory. The dirnode.build() method will suppress
1618 # the build if it's the default builder.
1619 SCons.Node.Node.build(dirnode)
1620 dirnode.get_executor().nullify()
1621 # The build() action may or may not have actually
1622 # created the directory, depending on whether the -n
1623 # option was used or not. Delete the _exists and
1624 # _rexists attributes so they can be reevaluated.
1629 def multiple_side_effect_has_builder(self):
1631 return self.builder is not MkdirBuilder and self.has_builder()
1633 def alter_targets(self):
1634 """Return any corresponding targets in a variant directory.
1636 return self.fs.variant_dir_target_climb(self, self, [])
1638 def scanner_key(self):
1639 """A directory does not get scanned."""
1642 def get_text_contents(self):
1643 """We already emit things in text, so just return the binary
1645 return self.get_contents()
1647 def get_contents(self):
1648 """Return content signatures and names of all our children
1649 separated by new-lines. Ensure that the nodes are sorted."""
1651 name_cmp = lambda a, b: cmp(a.name, b.name)
1652 sorted_children = self.children()[:]
1653 sorted_children.sort(name_cmp)
1654 for node in sorted_children:
1655 contents.append('%s %s\n' % (node.get_csig(), node.name))
1656 return string.join(contents, '')
1659 """Compute the content signature for Directory nodes. In
1660 general, this is not needed and the content signature is not
1661 stored in the DirNodeInfo. However, if get_contents on a Dir
1662 node is called which has a child directory, the child
1663 directory should return the hash of its contents."""
1664 contents = self.get_contents()
1665 return SCons.Util.MD5signature(contents)
1667 def do_duplicate(self, src):
1670 changed_since_last_build = SCons.Node.Node.state_has_changed
1672 def is_up_to_date(self):
1673 """If any child is not up-to-date, then this directory isn't,
1675 if self.builder is not MkdirBuilder and not self.exists():
1677 up_to_date = SCons.Node.up_to_date
1678 for kid in self.children():
1679 if kid.get_state() > up_to_date:
1684 if not self.exists():
1685 norm_name = _my_normcase(self.name)
1686 for dir in self.dir.get_all_rdirs():
1687 try: node = dir.entries[norm_name]
1688 except KeyError: node = dir.dir_on_disk(self.name)
1689 if node and node.exists() and \
1690 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1695 """Return the .sconsign file info for this directory,
1696 creating it first if necessary."""
1697 if not self._sconsign:
1698 import SCons.SConsign
1699 self._sconsign = SCons.SConsign.ForDirectory(self)
1700 return self._sconsign
1703 """Dir has a special need for srcnode()...if we
1704 have a srcdir attribute set, then that *is* our srcnode."""
1707 return Base.srcnode(self)
1709 def get_timestamp(self):
1710 """Return the latest timestamp from among our children"""
1712 for kid in self.children():
1713 if kid.get_timestamp() > stamp:
1714 stamp = kid.get_timestamp()
1717 def entry_abspath(self, name):
1718 return self.abspath + os.sep + name
1720 def entry_labspath(self, name):
1721 return self.labspath + '/' + name
1723 def entry_path(self, name):
1724 return self.path + os.sep + name
1726 def entry_tpath(self, name):
1727 return self.tpath + os.sep + name
1729 def entry_exists_on_disk(self, name):
1731 d = self.on_disk_entries
1732 except AttributeError:
1735 entries = os.listdir(self.abspath)
1739 for entry in map(_my_normcase, entries):
1741 self.on_disk_entries = d
1742 if sys.platform == 'win32':
1743 name = _my_normcase(name)
1744 result = d.get(name)
1746 # Belt-and-suspenders for Windows: check directly for
1747 # 8.3 file names that don't show up in os.listdir().
1748 result = os.path.exists(self.abspath + os.sep + name)
1752 return d.has_key(name)
1754 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1756 def srcdir_list(self):
1758 return self._memo['srcdir_list']
1768 result.append(dir.srcdir.Dir(dirname))
1769 dirname = dir.name + os.sep + dirname
1772 self._memo['srcdir_list'] = result
1776 def srcdir_duplicate(self, name):
1777 for dir in self.srcdir_list():
1778 if self.is_under(dir):
1779 # We shouldn't source from something in the build path;
1780 # variant_dir is probably under src_dir, in which case
1781 # we are reflecting.
1783 if dir.entry_exists_on_disk(name):
1784 srcnode = dir.Entry(name).disambiguate()
1786 node = self.Entry(name).disambiguate()
1787 node.do_duplicate(srcnode)
1793 def _srcdir_find_file_key(self, filename):
1796 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1798 def srcdir_find_file(self, filename):
1800 memo_dict = self._memo['srcdir_find_file']
1803 self._memo['srcdir_find_file'] = memo_dict
1806 return memo_dict[filename]
1811 if (isinstance(node, File) or isinstance(node, Entry)) and \
1812 (node.is_derived() or node.exists()):
1816 norm_name = _my_normcase(filename)
1818 for rdir in self.get_all_rdirs():
1819 try: node = rdir.entries[norm_name]
1820 except KeyError: node = rdir.file_on_disk(filename)
1821 else: node = func(node)
1823 result = (node, self)
1824 memo_dict[filename] = result
1827 for srcdir in self.srcdir_list():
1828 for rdir in srcdir.get_all_rdirs():
1829 try: node = rdir.entries[norm_name]
1830 except KeyError: node = rdir.file_on_disk(filename)
1831 else: node = func(node)
1833 result = (File(filename, self, self.fs), srcdir)
1834 memo_dict[filename] = result
1837 result = (None, None)
1838 memo_dict[filename] = result
1841 def dir_on_disk(self, name):
1842 if self.entry_exists_on_disk(name):
1843 try: return self.Dir(name)
1844 except TypeError: pass
1845 node = self.srcdir_duplicate(name)
1846 if isinstance(node, File):
1850 def file_on_disk(self, name):
1851 if self.entry_exists_on_disk(name) or \
1852 diskcheck_rcs(self, name) or \
1853 diskcheck_sccs(self, name):
1854 try: return self.File(name)
1855 except TypeError: pass
1856 node = self.srcdir_duplicate(name)
1857 if isinstance(node, Dir):
1861 def walk(self, func, arg):
1863 Walk this directory tree by calling the specified function
1864 for each directory in the tree.
1866 This behaves like the os.path.walk() function, but for in-memory
1867 Node.FS.Dir objects. The function takes the same arguments as
1868 the functions passed to os.path.walk():
1870 func(arg, dirname, fnames)
1872 Except that "dirname" will actually be the directory *Node*,
1873 not the string. The '.' and '..' entries are excluded from
1874 fnames. The fnames list may be modified in-place to filter the
1875 subdirectories visited or otherwise impose a specific order.
1876 The "arg" argument is always passed to func() and may be used
1877 in any way (or ignored, passing None is common).
1879 entries = self.entries
1880 names = entries.keys()
1883 func(arg, self, names)
1884 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1885 for dirname in filter(select_dirs, names):
1886 entries[dirname].walk(func, arg)
1888 def glob(self, pathname, ondisk=True, source=False, strings=False):
1890 Returns a list of Nodes (or strings) matching a specified
1893 Pathname patterns follow UNIX shell semantics: * matches
1894 any-length strings of any characters, ? matches any character,
1895 and [] can enclose lists or ranges of characters. Matches do
1896 not span directory separators.
1898 The matches take into account Repositories, returning local
1899 Nodes if a corresponding entry exists in a Repository (either
1900 an in-memory Node or something on disk).
1902 By defafult, the glob() function matches entries that exist
1903 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1904 argument to False (or some other non-true value) causes the glob()
1905 function to only match in-memory Nodes. The default behavior is
1906 to return both the on-disk and in-memory Nodes.
1908 The "source" argument, when true, specifies that corresponding
1909 source Nodes must be returned if you're globbing in a build
1910 directory (initialized with VariantDir()). The default behavior
1911 is to return Nodes local to the VariantDir().
1913 The "strings" argument, when true, returns the matches as strings,
1914 not Nodes. The strings are path names relative to this directory.
1916 The underlying algorithm is adapted from the glob.glob() function
1917 in the Python library (but heavily modified), and uses fnmatch()
1920 dirname, basename = os.path.split(pathname)
1922 return self._glob1(basename, ondisk, source, strings)
1923 if has_glob_magic(dirname):
1924 list = self.glob(dirname, ondisk, source, strings=False)
1926 list = [self.Dir(dirname, create=True)]
1929 r = dir._glob1(basename, ondisk, source, strings)
1931 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1933 result.sort(lambda a, b: cmp(str(a), str(b)))
1936 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1938 Globs for and returns a list of entry names matching a single
1939 pattern in this directory.
1941 This searches any repositories and source directories for
1942 corresponding entries and returns a Node (or string) relative
1943 to the current directory if an entry is found anywhere.
1945 TODO: handle pattern with no wildcard
1947 search_dir_list = self.get_all_rdirs()
1948 for srcdir in self.srcdir_list():
1949 search_dir_list.extend(srcdir.get_all_rdirs())
1951 selfEntry = self.Entry
1953 for dir in search_dir_list:
1954 # We use the .name attribute from the Node because the keys of
1955 # the dir.entries dictionary are normalized (that is, all upper
1956 # case) on case-insensitive systems like Windows.
1957 #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
1958 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1959 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1960 names.extend(node_names)
1962 # Make sure the working directory (self) actually has
1963 # entries for all Nodes in repositories or variant dirs.
1964 for name in node_names: selfEntry(name)
1967 disk_names = os.listdir(dir.abspath)
1970 names.extend(disk_names)
1972 # We're going to return corresponding Nodes in
1973 # the local directory, so we need to make sure
1974 # those Nodes exist. We only want to create
1975 # Nodes for the entries that will match the
1976 # specified pattern, though, which means we
1977 # need to filter the list here, even though
1978 # the overall list will also be filtered later,
1979 # after we exit this loop.
1980 if pattern[0] != '.':
1981 #disk_names = [ d for d in disk_names if d[0] != '.' ]
1982 disk_names = filter(lambda x: x[0] != '.', disk_names)
1983 disk_names = fnmatch.filter(disk_names, pattern)
1984 dirEntry = dir.Entry
1985 for name in disk_names:
1986 # Add './' before disk filename so that '#' at
1987 # beginning of filename isn't interpreted.
1989 node = dirEntry(name).disambiguate()
1991 if n.__class__ != node.__class__:
1992 n.__class__ = node.__class__
1996 if pattern[0] != '.':
1997 #names = [ n for n in names if n[0] != '.' ]
1998 names = filter(lambda x: x[0] != '.', names)
1999 names = fnmatch.filter(names, pattern)
2004 #return [ self.entries[_my_normcase(n)] for n in names ]
2005 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
2008 """A class for the root directory of a file system.
2010 This is the same as a Dir class, except that the path separator
2011 ('/' or '\\') is actually part of the name, so we don't need to
2012 add a separator when creating the path names of entries within
2015 def __init__(self, name, fs):
2016 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
2017 # We're going to be our own parent directory (".." entry and .dir
2018 # attribute) so we have to set up some values so Base.__init__()
2019 # won't gag won't it calls some of our methods.
2024 self.path_elements = []
2027 Base.__init__(self, name, self, fs)
2029 # Now set our paths to what we really want them to be: the
2030 # initial drive letter (the name) plus the directory separator,
2031 # except for the "lookup abspath," which does not have the
2033 self.abspath = name + os.sep
2035 self.path = name + os.sep
2036 self.tpath = name + os.sep
2039 self._lookupDict = {}
2041 # The // and os.sep + os.sep entries are necessary because
2042 # os.path.normpath() seems to preserve double slashes at the
2043 # beginning of a path (presumably for UNC path names), but
2044 # collapses triple slashes to a single slash.
2045 self._lookupDict[''] = self
2046 self._lookupDict['/'] = self
2047 self._lookupDict['//'] = self
2048 self._lookupDict[os.sep] = self
2049 self._lookupDict[os.sep + os.sep] = self
2051 def must_be_same(self, klass):
2054 Base.must_be_same(self, klass)
2056 def _lookup_abs(self, p, klass, create=1):
2058 Fast (?) lookup of a *normalized* absolute path.
2060 This method is intended for use by internal lookups with
2061 already-normalized path data. For general-purpose lookups,
2062 use the FS.Entry(), FS.Dir() or FS.File() methods.
2064 The caller is responsible for making sure we're passed a
2065 normalized absolute path; we merely let Python's dictionary look
2066 up and return the One True Node.FS object for the path.
2068 If no Node for the specified "p" doesn't already exist, and
2069 "create" is specified, the Node may be created after recursive
2070 invocation to find or create the parent directory or directories.
2074 result = self._lookupDict[k]
2077 raise SCons.Errors.UserError
2078 # There is no Node for this path name, and we're allowed
2080 dir_name, file_name = os.path.split(p)
2081 dir_node = self._lookup_abs(dir_name, Dir)
2082 result = klass(file_name, dir_node, self.fs)
2084 # Double-check on disk (as configured) that the Node we
2085 # created matches whatever is out there in the real world.
2086 result.diskcheck_match()
2088 self._lookupDict[k] = result
2089 dir_node.entries[_my_normcase(file_name)] = result
2090 dir_node.implicit = None
2092 # There is already a Node for this path name. Allow it to
2093 # complain if we were looking for an inappropriate type.
2094 result.must_be_same(klass)
2100 def entry_abspath(self, name):
2101 return self.abspath + name
2103 def entry_labspath(self, name):
2106 def entry_path(self, name):
2107 return self.path + name
2109 def entry_tpath(self, name):
2110 return self.tpath + name
2112 def is_under(self, dir):
2124 def src_builder(self):
2127 class FileNodeInfo(SCons.Node.NodeInfoBase):
2128 current_version_id = 1
2130 field_list = ['csig', 'timestamp', 'size']
2132 # This should get reset by the FS initialization.
2135 def str_to_node(self, s):
2139 drive, s = os.path.splitdrive(s)
2141 root = self.fs.get_root(drive)
2142 if not os.path.isabs(s):
2143 s = top.labspath + '/' + s
2144 return root._lookup_abs(s, Entry)
2146 class FileBuildInfo(SCons.Node.BuildInfoBase):
2147 current_version_id = 1
2149 def convert_to_sconsign(self):
2151 Converts this FileBuildInfo object for writing to a .sconsign file
2153 This replaces each Node in our various dependency lists with its
2154 usual string representation: relative to the top-level SConstruct
2155 directory, or an absolute path if it's outside.
2163 except AttributeError:
2166 s = string.replace(s, os.sep, '/')
2168 for attr in ['bsources', 'bdepends', 'bimplicit']:
2170 val = getattr(self, attr)
2171 except AttributeError:
2174 setattr(self, attr, map(node_to_str, val))
2175 def convert_from_sconsign(self, dir, name):
2177 Converts a newly-read FileBuildInfo object for in-SCons use
2179 For normal up-to-date checking, we don't have any conversion to
2180 perform--but we're leaving this method here to make that clear.
2183 def prepare_dependencies(self):
2185 Prepares a FileBuildInfo object for explaining what changed
2187 The bsources, bdepends and bimplicit lists have all been
2188 stored on disk as paths relative to the top-level SConstruct
2189 directory. Convert the strings to actual Nodes (for use by the
2190 --debug=explain code and --implicit-cache).
2193 ('bsources', 'bsourcesigs'),
2194 ('bdepends', 'bdependsigs'),
2195 ('bimplicit', 'bimplicitsigs'),
2197 for (nattr, sattr) in attrs:
2199 strings = getattr(self, nattr)
2200 nodeinfos = getattr(self, sattr)
2201 except AttributeError:
2204 for s, ni in izip(strings, nodeinfos):
2205 if not isinstance(s, SCons.Node.Node):
2206 s = ni.str_to_node(s)
2208 setattr(self, nattr, nodes)
2209 def format(self, names=0):
2211 bkids = self.bsources + self.bdepends + self.bimplicit
2212 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
2213 for bkid, bkidsig in izip(bkids, bkidsigs):
2214 result.append(str(bkid) + ': ' +
2215 string.join(bkidsig.format(names=names), ' '))
2216 result.append('%s [%s]' % (self.bactsig, self.bact))
2217 return string.join(result, '\n')
2220 """A class for files in a file system.
2223 memoizer_counters = []
2225 NodeInfo = FileNodeInfo
2226 BuildInfo = FileBuildInfo
2230 def diskcheck_match(self):
2231 diskcheck_match(self, self.isdir,
2232 "Directory %s found where file expected.")
2234 def __init__(self, name, directory, fs):
2235 if __debug__: logInstanceCreation(self, 'Node.FS.File')
2236 Base.__init__(self, name, directory, fs)
2239 def Entry(self, name):
2240 """Create an entry node named 'name' relative to
2241 the directory of this file."""
2242 return self.dir.Entry(name)
2244 def Dir(self, name, create=True):
2245 """Create a directory node named 'name' relative to
2246 the directory of this file."""
2247 return self.dir.Dir(name, create=create)
2249 def Dirs(self, pathlist):
2250 """Create a list of directories relative to the SConscript
2251 directory of this file."""
2253 # return [self.Dir(p) for p in pathlist]
2254 return map(lambda p, s=self: s.Dir(p), pathlist)
2256 def File(self, name):
2257 """Create a file node named 'name' relative to
2258 the directory of this file."""
2259 return self.dir.File(name)
2261 #def generate_build_dict(self):
2262 # """Return an appropriate dictionary of values for building
2264 # return {'Dir' : self.Dir,
2265 # 'File' : self.File,
2266 # 'RDirs' : self.RDirs}
2269 """Turn a file system node into a File object."""
2270 self.scanner_paths = {}
2271 if not hasattr(self, '_local'):
2274 # If there was already a Builder set on this entry, then
2275 # we need to make sure we call the target-decider function,
2276 # not the source-decider. Reaching in and doing this by hand
2277 # is a little bogus. We'd prefer to handle this by adding
2278 # an Entry.builder_set() method that disambiguates like the
2279 # other methods, but that starts running into problems with the
2280 # fragile way we initialize Dir Nodes with their Mkdir builders,
2281 # yet still allow them to be overridden by the user. Since it's
2282 # not clear right now how to fix that, stick with what works
2283 # until it becomes clear...
2284 if self.has_builder():
2285 self.changed_since_last_build = self.decide_target
2287 def scanner_key(self):
2288 return self.get_suffix()
2290 def get_contents(self):
2291 if not self.rexists():
2293 fname = self.rfile().abspath
2295 contents = open(fname, "rb").read()
2296 except EnvironmentError, e:
2305 get_text_contents = get_contents
2307 # This attempts to figure out what the encoding of the text is
2308 # based upon the BOM bytes, and then decodes the contents so that
2309 # it's a valid python string.
2310 def get_text_contents(self):
2311 contents = self.get_contents()
2312 if contents.startswith(codecs.BOM_UTF8):
2313 contents = contents.decode('utf-8')
2314 elif contents.startswith(codecs.BOM_UTF16):
2315 contents = contents.decode('utf-16')
2318 def get_content_hash(self):
2320 Compute and return the MD5 hash for this file.
2322 if not self.rexists():
2323 return SCons.Util.MD5signature('')
2324 fname = self.rfile().abspath
2326 cs = SCons.Util.MD5filesignature(fname,
2327 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2328 except EnvironmentError, e:
2335 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2339 return self._memo['get_size']
2344 size = self.rfile().getsize()
2348 self._memo['get_size'] = size
2352 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2354 def get_timestamp(self):
2356 return self._memo['get_timestamp']
2361 timestamp = self.rfile().getmtime()
2365 self._memo['get_timestamp'] = timestamp
2369 def store_info(self):
2370 # Merge our build information into the already-stored entry.
2371 # This accomodates "chained builds" where a file that's a target
2372 # in one build (SConstruct file) is a source in a different build.
2373 # See test/chained-build.py for the use case.
2375 self.dir.sconsign().store_info(self.name, self)
2377 convert_copy_attrs = [
2387 convert_sig_attrs = [
2393 def convert_old_entry(self, old_entry):
2394 # Convert a .sconsign entry from before the Big Signature
2395 # Refactoring, doing what we can to convert its information
2396 # to the new .sconsign entry format.
2398 # The old format looked essentially like this:
2407 # .bsourcesigs ("signature" list)
2409 # .bdependsigs ("signature" list)
2411 # .bimplicitsigs ("signature" list)
2415 # The new format looks like this:
2422 # .binfo (BuildInfo)
2424 # .bsourcesigs (NodeInfo list)
2430 # .bdependsigs (NodeInfo list)
2436 # .bimplicitsigs (NodeInfo list)
2444 # The basic idea of the new structure is that a NodeInfo always
2445 # holds all available information about the state of a given Node
2446 # at a certain point in time. The various .b*sigs lists can just
2447 # be a list of pointers to the .ninfo attributes of the different
2448 # dependent nodes, without any copying of information until it's
2449 # time to pickle it for writing out to a .sconsign file.
2451 # The complicating issue is that the *old* format only stored one
2452 # "signature" per dependency, based on however the *last* build
2453 # was configured. We don't know from just looking at it whether
2454 # it was a build signature, a content signature, or a timestamp
2455 # "signature". Since we no longer use build signatures, the
2456 # best we can do is look at the length and if it's thirty two,
2457 # assume that it was (or might have been) a content signature.
2458 # If it was actually a build signature, then it will cause a
2459 # rebuild anyway when it doesn't match the new content signature,
2460 # but that's probably the best we can do.
2461 import SCons.SConsign
2462 new_entry = SCons.SConsign.SConsignEntry()
2463 new_entry.binfo = self.new_binfo()
2464 binfo = new_entry.binfo
2465 for attr in self.convert_copy_attrs:
2467 value = getattr(old_entry, attr)
2468 except AttributeError:
2470 setattr(binfo, attr, value)
2471 delattr(old_entry, attr)
2472 for attr in self.convert_sig_attrs:
2474 sig_list = getattr(old_entry, attr)
2475 except AttributeError:
2478 for sig in sig_list:
2479 ninfo = self.new_ninfo()
2483 ninfo.timestamp = sig
2485 setattr(binfo, attr, value)
2486 delattr(old_entry, attr)
2489 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2491 def get_stored_info(self):
2493 return self._memo['get_stored_info']
2498 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2499 except (KeyError, EnvironmentError):
2500 import SCons.SConsign
2501 sconsign_entry = SCons.SConsign.SConsignEntry()
2502 sconsign_entry.binfo = self.new_binfo()
2503 sconsign_entry.ninfo = self.new_ninfo()
2505 if isinstance(sconsign_entry, FileBuildInfo):
2506 # This is a .sconsign file from before the Big Signature
2507 # Refactoring; convert it as best we can.
2508 sconsign_entry = self.convert_old_entry(sconsign_entry)
2510 delattr(sconsign_entry.ninfo, 'bsig')
2511 except AttributeError:
2514 self._memo['get_stored_info'] = sconsign_entry
2516 return sconsign_entry
2518 def get_stored_implicit(self):
2519 binfo = self.get_stored_info().binfo
2520 binfo.prepare_dependencies()
2521 try: return binfo.bimplicit
2522 except AttributeError: return None
2524 def rel_path(self, other):
2525 return self.dir.rel_path(other)
2527 def _get_found_includes_key(self, env, scanner, path):
2528 return (id(env), id(scanner), path)
2530 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2532 def get_found_includes(self, env, scanner, path):
2533 """Return the included implicit dependencies in this file.
2534 Cache results so we only scan the file once per path
2535 regardless of how many times this information is requested.
2537 memo_key = (id(env), id(scanner), path)
2539 memo_dict = self._memo['get_found_includes']
2542 self._memo['get_found_includes'] = memo_dict
2545 return memo_dict[memo_key]
2550 # result = [n.disambiguate() for n in scanner(self, env, path)]
2551 result = scanner(self, env, path)
2552 result = map(lambda N: N.disambiguate(), result)
2556 memo_dict[memo_key] = result
2560 def _createDir(self):
2561 # ensure that the directories for this node are
2565 def retrieve_from_cache(self):
2566 """Try to retrieve the node's content from a cache
2568 This method is called from multiple threads in a parallel build,
2569 so only do thread safe stuff here. Do thread unsafe stuff in
2572 Returns true iff the node was successfully retrieved.
2576 if not self.is_derived():
2578 return self.get_build_env().get_CacheDir().retrieve(self)
2582 Called just after this node is successfully built.
2584 # Push this file out to cache before the superclass Node.built()
2585 # method has a chance to clear the build signature, which it
2586 # will do if this file has a source scanner.
2588 # We have to clear the memoized values *before* we push it to
2589 # cache so that the memoization of the self.exists() return
2590 # value doesn't interfere.
2591 self.clear_memoized_values()
2593 self.get_build_env().get_CacheDir().push(self)
2594 SCons.Node.Node.built(self)
2598 self.get_build_env().get_CacheDir().push_if_forced(self)
2600 ninfo = self.get_ninfo()
2602 csig = self.get_max_drift_csig()
2606 ninfo.timestamp = self.get_timestamp()
2607 ninfo.size = self.get_size()
2609 if not self.has_builder():
2610 # This is a source file, but it might have been a target file
2611 # in another build that included more of the DAG. Copy
2612 # any build information that's stored in the .sconsign file
2613 # into our binfo object so it doesn't get lost.
2614 old = self.get_stored_info()
2615 self.get_binfo().__dict__.update(old.binfo.__dict__)
2619 def find_src_builder(self):
2622 scb = self.dir.src_builder()
2624 if diskcheck_sccs(self.dir, self.name):
2625 scb = get_DefaultSCCSBuilder()
2626 elif diskcheck_rcs(self.dir, self.name):
2627 scb = get_DefaultRCSBuilder()
2633 except AttributeError:
2636 self.builder_set(scb)
2639 def has_src_builder(self):
2640 """Return whether this Node has a source builder or not.
2642 If this Node doesn't have an explicit source code builder, this
2643 is where we figure out, on the fly, if there's a transparent
2644 source code builder for it.
2646 Note that if we found a source builder, we also set the
2647 self.builder attribute, so that all of the methods that actually
2648 *build* this file don't have to do anything different.
2652 except AttributeError:
2653 scb = self.sbuilder = self.find_src_builder()
2654 return scb is not None
2656 def alter_targets(self):
2657 """Return any corresponding targets in a variant directory.
2659 if self.is_derived():
2661 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2663 def _rmv_existing(self):
2664 self.clear_memoized_values()
2665 e = Unlink(self, [], None)
2666 if isinstance(e, SCons.Errors.BuildError):
2670 # Taskmaster interface subsystem
2673 def make_ready(self):
2674 self.has_src_builder()
2678 """Prepare for this file to be created."""
2679 SCons.Node.Node.prepare(self)
2681 if self.get_state() != SCons.Node.up_to_date:
2683 if self.is_derived() and not self.precious:
2684 self._rmv_existing()
2688 except SCons.Errors.StopError, drive:
2689 desc = "No drive `%s' for target `%s'." % (drive, self)
2690 raise SCons.Errors.StopError, desc
2697 """Remove this file."""
2698 if self.exists() or self.islink():
2699 self.fs.unlink(self.path)
2703 def do_duplicate(self, src):
2705 Unlink(self, None, None)
2706 e = Link(self, src, None)
2707 if isinstance(e, SCons.Errors.BuildError):
2708 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2709 raise SCons.Errors.StopError, desc
2711 # The Link() action may or may not have actually
2712 # created the file, depending on whether the -n
2713 # option was used or not. Delete the _exists and
2714 # _rexists attributes so they can be reevaluated.
2717 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2721 return self._memo['exists']
2724 # Duplicate from source path if we are set up to do this.
2725 if self.duplicate and not self.is_derived() and not self.linked:
2726 src = self.srcnode()
2728 # At this point, src is meant to be copied in a variant directory.
2730 if src.abspath != self.abspath:
2732 self.do_duplicate(src)
2733 # Can't return 1 here because the duplication might
2734 # not actually occur if the -n option is being used.
2736 # The source file does not exist. Make sure no old
2737 # copy remains in the variant directory.
2738 if Base.exists(self) or self.islink():
2739 self.fs.unlink(self.path)
2740 # Return None explicitly because the Base.exists() call
2741 # above will have cached its value if the file existed.
2742 self._memo['exists'] = None
2744 result = Base.exists(self)
2745 self._memo['exists'] = result
2749 # SIGNATURE SUBSYSTEM
2752 def get_max_drift_csig(self):
2754 Returns the content signature currently stored for this node
2755 if it's been unmodified longer than the max_drift value, or the
2756 max_drift value is 0. Returns None otherwise.
2758 old = self.get_stored_info()
2759 mtime = self.get_timestamp()
2761 max_drift = self.fs.max_drift
2763 if (time.time() - mtime) > max_drift:
2766 if n.timestamp and n.csig and n.timestamp == mtime:
2768 except AttributeError:
2770 elif max_drift == 0:
2772 return old.ninfo.csig
2773 except AttributeError:
2780 Generate a node's content signature, the digested signature
2784 cache - alternate node to use for the signature cache
2785 returns - the content signature
2787 ninfo = self.get_ninfo()
2790 except AttributeError:
2793 csig = self.get_max_drift_csig()
2797 if self.get_size() < SCons.Node.FS.File.md5_chunksize:
2798 contents = self.get_contents()
2800 csig = self.get_content_hash()
2802 # This can happen if there's actually a directory on-disk,
2803 # which can be the case if they've disabled disk checks,
2804 # or if an action with a File target actually happens to
2805 # create a same-named directory by mistake.
2809 csig = SCons.Util.MD5signature(contents)
2816 # DECISION SUBSYSTEM
2819 def builder_set(self, builder):
2820 SCons.Node.Node.builder_set(self, builder)
2821 self.changed_since_last_build = self.decide_target
2823 def changed_content(self, target, prev_ni):
2824 cur_csig = self.get_csig()
2826 return cur_csig != prev_ni.csig
2827 except AttributeError:
2830 def changed_state(self, target, prev_ni):
2831 return self.state != SCons.Node.up_to_date
2833 def changed_timestamp_then_content(self, target, prev_ni):
2834 if not self.changed_timestamp_match(target, prev_ni):
2836 self.get_ninfo().csig = prev_ni.csig
2837 except AttributeError:
2840 return self.changed_content(target, prev_ni)
2842 def changed_timestamp_newer(self, target, prev_ni):
2844 return self.get_timestamp() > target.get_timestamp()
2845 except AttributeError:
2848 def changed_timestamp_match(self, target, prev_ni):
2850 return self.get_timestamp() != prev_ni.timestamp
2851 except AttributeError:
2854 def decide_source(self, target, prev_ni):
2855 return target.get_build_env().decide_source(self, target, prev_ni)
2857 def decide_target(self, target, prev_ni):
2858 return target.get_build_env().decide_target(self, target, prev_ni)
2860 # Initialize this Node's decider function to decide_source() because
2861 # every file is a source file until it has a Builder attached...
2862 changed_since_last_build = decide_source
2864 def is_up_to_date(self):
2866 if T: Trace('is_up_to_date(%s):' % self)
2867 if not self.exists():
2868 if T: Trace(' not self.exists():')
2869 # The file doesn't exist locally...
2872 # ...but there is one in a Repository...
2873 if not self.changed(r):
2874 if T: Trace(' changed(%s):' % r)
2875 # ...and it's even up-to-date...
2877 # ...and they'd like a local copy.
2878 e = LocalCopy(self, r, None)
2879 if isinstance(e, SCons.Errors.BuildError):
2885 if T: Trace(' None\n')
2889 if T: Trace(' self.exists(): %s\n' % r)
2892 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2896 return self._memo['rfile']
2900 if not self.exists():
2901 norm_name = _my_normcase(self.name)
2902 for dir in self.dir.get_all_rdirs():
2903 try: node = dir.entries[norm_name]
2904 except KeyError: node = dir.file_on_disk(self.name)
2905 if node and node.exists() and \
2906 (isinstance(node, File) or isinstance(node, Entry) \
2907 or not node.is_derived()):
2909 # Copy over our local attributes to the repository
2910 # Node so we identify shared object files in the
2911 # repository and don't assume they're static.
2913 # This isn't perfect; the attribute would ideally
2914 # be attached to the object in the repository in
2915 # case it was built statically in the repository
2916 # and we changed it to shared locally, but that's
2917 # rarely the case and would only occur if you
2918 # intentionally used the same suffix for both
2919 # shared and static objects anyway. So this
2920 # should work well in practice.
2921 result.attributes = self.attributes
2923 self._memo['rfile'] = result
2927 return str(self.rfile())
2929 def get_cachedir_csig(self):
2931 Fetch a Node's content signature for purposes of computing
2932 another Node's cachesig.
2934 This is a wrapper around the normal get_csig() method that handles
2935 the somewhat obscure case of using CacheDir with the -n option.
2936 Any files that don't exist would normally be "built" by fetching
2937 them from the cache, but the normal get_csig() method will try
2938 to open up the local file, which doesn't exist because the -n
2939 option meant we didn't actually pull the file from cachedir.
2940 But since the file *does* actually exist in the cachedir, we
2941 can use its contents for the csig.
2944 return self.cachedir_csig
2945 except AttributeError:
2948 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2949 if not self.exists() and cachefile and os.path.exists(cachefile):
2950 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
2951 SCons.Node.FS.File.md5_chunksize * 1024)
2953 self.cachedir_csig = self.get_csig()
2954 return self.cachedir_csig
2956 def get_cachedir_bsig(self):
2958 return self.cachesig
2959 except AttributeError:
2962 # Add the path to the cache signature, because multiple
2963 # targets built by the same action will all have the same
2964 # build signature, and we have to differentiate them somehow.
2965 children = self.children()
2966 executor = self.get_executor()
2967 # sigs = [n.get_cachedir_csig() for n in children]
2968 sigs = map(lambda n: n.get_cachedir_csig(), children)
2969 sigs.append(SCons.Util.MD5signature(executor.get_contents()))
2970 sigs.append(self.path)
2971 result = self.cachesig = SCons.Util.MD5collect(sigs)
2977 def get_default_fs():
2986 if SCons.Memoize.use_memoizer:
2987 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2989 memoizer_counters = []
2994 def filedir_lookup(self, p, fd=None):
2996 A helper method for find_file() that looks up a directory for
2997 a file we're trying to find. This only creates the Dir Node if
2998 it exists on-disk, since if the directory doesn't exist we know
2999 we won't find any files in it... :-)
3001 It would be more compact to just use this as a nested function
3002 with a default keyword argument (see the commented-out version
3003 below), but that doesn't work unless you have nested scopes,
3004 so we define it here just so this work under Python 1.5.2.
3007 fd = self.default_filedir
3008 dir, name = os.path.split(fd)
3009 drive, d = os.path.splitdrive(dir)
3010 if not name and d[:1] in ('/', os.sep):
3011 #return p.fs.get_root(drive).dir_on_disk(name)
3012 return p.fs.get_root(drive)
3014 p = self.filedir_lookup(p, dir)
3017 norm_name = _my_normcase(name)
3019 node = p.entries[norm_name]
3021 return p.dir_on_disk(name)
3022 if isinstance(node, Dir):
3024 if isinstance(node, Entry):
3025 node.must_be_same(Dir)
3029 def _find_file_key(self, filename, paths, verbose=None):
3030 return (filename, paths)
3032 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
3034 def find_file(self, filename, paths, verbose=None):
3036 find_file(str, [Dir()]) -> [nodes]
3038 filename - a filename to find
3039 paths - a list of directory path *nodes* to search in. Can be
3040 represented as a list, a tuple, or a callable that is
3041 called with no arguments and returns the list or tuple.
3043 returns - the node created from the found file.
3045 Find a node corresponding to either a derived file or a file
3046 that exists already.
3048 Only the first file found is returned, and none is returned
3049 if no file is found.
3051 memo_key = self._find_file_key(filename, paths)
3053 memo_dict = self._memo['find_file']
3056 self._memo['find_file'] = memo_dict
3059 return memo_dict[memo_key]
3063 if verbose and not callable(verbose):
3064 if not SCons.Util.is_String(verbose):
3065 verbose = "find_file"
3066 verbose = ' %s: ' % verbose
3067 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
3069 filedir, filename = os.path.split(filename)
3071 # More compact code that we can't use until we drop
3072 # support for Python 1.5.2:
3074 #def filedir_lookup(p, fd=filedir):
3076 # A helper function that looks up a directory for a file
3077 # we're trying to find. This only creates the Dir Node
3078 # if it exists on-disk, since if the directory doesn't
3079 # exist we know we won't find any files in it... :-)
3081 # dir, name = os.path.split(fd)
3083 # p = filedir_lookup(p, dir)
3086 # norm_name = _my_normcase(name)
3088 # node = p.entries[norm_name]
3090 # return p.dir_on_disk(name)
3091 # if isinstance(node, Dir):
3093 # if isinstance(node, Entry):
3094 # node.must_be_same(Dir)
3096 # if isinstance(node, Dir) or isinstance(node, Entry):
3099 #paths = filter(None, map(filedir_lookup, paths))
3101 self.default_filedir = filedir
3102 paths = filter(None, map(self.filedir_lookup, paths))
3107 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3108 node, d = dir.srcdir_find_file(filename)
3111 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3115 memo_dict[memo_key] = result
3119 find_file = FileFinder().find_file
3122 def invalidate_node_memos(targets):
3124 Invalidate the memoized values of all Nodes (files or directories)
3125 that are associated with the given entries. Has been added to
3126 clear the cache of nodes affected by a direct execution of an
3127 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3128 inconsistent if the action is run through Execute(). The argument
3129 `targets` can be a single Node object or filename, or a sequence
3132 from traceback import extract_stack
3134 # First check if the cache really needs to be flushed. Only
3135 # actions run in the SConscript with Execute() seem to be
3136 # affected. XXX The way to check if Execute() is in the stacktrace
3137 # is a very dirty hack and should be replaced by a more sensible
3139 for f in extract_stack():
3140 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3143 # Dont have to invalidate, so return
3146 if not SCons.Util.is_List(targets):
3149 for entry in targets:
3150 # If the target is a Node object, clear the cache. If it is a
3151 # filename, look up potentially existing Node object first.
3153 entry.clear_memoized_values()
3154 except AttributeError:
3155 # Not a Node object, try to look up Node by filename. XXX
3156 # This creates Node objects even for those filenames which
3157 # do not correspond to an existing Node object.
3158 node = get_default_fs().Entry(entry)
3160 node.clear_memoized_values()
3164 # indent-tabs-mode:nil
3166 # vim: set expandtab tabstop=4 shiftwidth=4: