5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
8 This holds a "default_fs" variable that should be initialized with an FS
9 that can be used by scripts or modules looking for the canonical default.
16 # Permission is hereby granted, free of charge, to any person obtaining
17 # a copy of this software and associated documentation files (the
18 # "Software"), to deal in the Software without restriction, including
19 # without limitation the rights to use, copy, modify, merge, publish,
20 # distribute, sublicense, and/or sell copies of the Software, and to
21 # permit persons to whom the Software is furnished to do so, subject to
22 # the following conditions:
24 # The above copyright notice and this permission notice shall be included
25 # in all copies or substantial portions of the Software.
27 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
28 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
29 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
39 from itertools import izip
51 from SCons.Debug import logInstanceCreation
55 import SCons.Node.Alias
60 from SCons.Debug import Trace
62 # The max_drift value: by default, use a cached signature value for
63 # any file that's been untouched for more than two days.
64 default_max_drift = 2*24*60*60
67 # We stringify these file system Nodes a lot. Turning a file system Node
68 # into a string is non-trivial, because the final string representation
69 # can depend on a lot of factors: whether it's a derived target or not,
70 # whether it's linked to a repository or source directory, and whether
71 # there's duplication going on. The normal technique for optimizing
72 # calculations like this is to memoize (cache) the string value, so you
73 # only have to do the calculation once.
75 # A number of the above factors, however, can be set after we've already
76 # been asked to return a string for a Node, because a Repository() or
77 # VariantDir() call or the like may not occur until later in SConscript
78 # files. So this variable controls whether we bother trying to save
79 # string values for Nodes. The wrapper interface can set this whenever
80 # they're done mucking with Repository and VariantDir and the other stuff,
81 # to let this module know it can start returning saved string values
86 def save_strings(val):
91 # Avoid unnecessary function calls by recording a Boolean value that
92 # tells us whether or not os.path.splitdrive() actually does anything
93 # on this system, and therefore whether we need to bother calling it
94 # when looking up path names in various methods below.
99 def initialize_do_splitdrive():
101 drive, path = os.path.splitdrive('X:/foo')
102 do_splitdrive = not not drive
104 initialize_do_splitdrive()
108 needs_normpath_check = None
110 def initialize_normpath_check():
112 Initialize the normpath_check regular expression.
114 This function is used by the unit tests to re-initialize the pattern
115 when testing for behavior with different values of os.sep.
117 global needs_normpath_check
119 pattern = r'.*/|\.$|\.\.$'
121 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
122 needs_normpath_check = re.compile(pattern)
124 initialize_normpath_check()
127 # SCons.Action objects for interacting with the outside world.
129 # The Node.FS methods in this module should use these actions to
130 # create and/or remove files and directories; they should *not* use
131 # os.{link,symlink,unlink,mkdir}(), etc., directly.
133 # Using these SCons.Action objects ensures that descriptions of these
134 # external activities are properly displayed, that the displays are
135 # suppressed when the -s (silent) option is used, and (most importantly)
136 # the actions are disabled when the the -n option is used, in which case
137 # there should be *no* changes to the external file system(s)...
140 if hasattr(os, 'link'):
141 def _hardlink_func(fs, src, dst):
142 # If the source is a symlink, we can't just hard-link to it
143 # because a relative symlink may point somewhere completely
144 # different. We must disambiguate the symlink and then
145 # hard-link the final destination file.
146 while fs.islink(src):
147 link = fs.readlink(src)
148 if not os.path.isabs(link):
151 src = os.path.join(os.path.dirname(src), link)
154 _hardlink_func = None
156 if hasattr(os, 'symlink'):
157 def _softlink_func(fs, src, dst):
160 _softlink_func = None
162 def _copy_func(fs, src, dest):
163 shutil.copy2(src, dest)
165 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
168 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
169 'hard-copy', 'soft-copy', 'copy']
171 Link_Funcs = [] # contains the callables of the specified duplication style
173 def set_duplicate(duplicate):
174 # Fill in the Link_Funcs list according to the argument
175 # (discarding those not available on the platform).
177 # Set up the dictionary that maps the argument names to the
178 # underlying implementations. We do this inside this function,
179 # not in the top-level module code, so that we can remap os.link
180 # and os.symlink for testing purposes.
182 'hard' : _hardlink_func,
183 'soft' : _softlink_func,
187 if not duplicate in Valid_Duplicates:
188 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
189 "should be in Valid_Duplicates")
192 for func in string.split(duplicate,'-'):
194 Link_Funcs.append(link_dict[func])
196 def LinkFunc(target, source, env):
197 # Relative paths cause problems with symbolic links, so
198 # we use absolute paths, which may be a problem for people
199 # who want to move their soft-linked src-trees around. Those
200 # people should use the 'hard-copy' mode, softlinks cannot be
201 # used for that; at least I have no idea how ...
202 src = source[0].abspath
203 dest = target[0].abspath
204 dir, file = os.path.split(dest)
205 if dir and not target[0].fs.isdir(dir):
208 # Set a default order of link functions.
209 set_duplicate('hard-soft-copy')
211 # Now link the files with the previously specified order.
212 for func in Link_Funcs:
216 except (IOError, OSError):
217 # An OSError indicates something happened like a permissions
218 # problem or an attempt to symlink across file-system
219 # boundaries. An IOError indicates something like the file
220 # not existing. In either case, keeping trying additional
221 # functions in the list and only raise an error if the last
223 if func == Link_Funcs[-1]:
224 # exception of the last link method (copy) are fatal
230 Link = SCons.Action.Action(LinkFunc, None)
231 def LocalString(target, source, env):
232 return 'Local copy of %s from %s' % (target[0], source[0])
234 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
236 def UnlinkFunc(target, source, env):
238 t.fs.unlink(t.abspath)
241 Unlink = SCons.Action.Action(UnlinkFunc, None)
243 def MkdirFunc(target, source, env):
246 t.fs.mkdir(t.abspath)
249 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
253 def get_MkdirBuilder():
255 if MkdirBuilder is None:
257 import SCons.Defaults
258 # "env" will get filled in by Executor.get_build_env()
259 # calling SCons.Defaults.DefaultEnvironment() when necessary.
260 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
264 target_scanner = SCons.Defaults.DirEntryScanner,
265 name = "MkdirBuilder")
273 DefaultSCCSBuilder = None
274 DefaultRCSBuilder = None
276 def get_DefaultSCCSBuilder():
277 global DefaultSCCSBuilder
278 if DefaultSCCSBuilder is None:
280 # "env" will get filled in by Executor.get_build_env()
281 # calling SCons.Defaults.DefaultEnvironment() when necessary.
282 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
283 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
285 name = "DefaultSCCSBuilder")
286 return DefaultSCCSBuilder
288 def get_DefaultRCSBuilder():
289 global DefaultRCSBuilder
290 if DefaultRCSBuilder is None:
292 # "env" will get filled in by Executor.get_build_env()
293 # calling SCons.Defaults.DefaultEnvironment() when necessary.
294 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
295 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
297 name = "DefaultRCSBuilder")
298 return DefaultRCSBuilder
300 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
301 _is_cygwin = sys.platform == "cygwin"
302 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
307 return string.upper(x)
312 def __init__(self, type, do, ignore):
318 self.__call__ = self.do
319 def set_ignore(self):
320 self.__call__ = self.ignore
322 if self.type in list:
327 def do_diskcheck_match(node, predicate, errorfmt):
330 # If calling the predicate() cached a None value from stat(),
331 # remove it so it doesn't interfere with later attempts to
332 # build this Node as we walk the DAG. (This isn't a great way
333 # to do this, we're reaching into an interface that doesn't
334 # really belong to us, but it's all about performance, so
335 # for now we'll just document the dependency...)
336 if node._memo['stat'] is None:
337 del node._memo['stat']
338 except (AttributeError, KeyError):
341 raise TypeError, errorfmt % node.abspath
343 def ignore_diskcheck_match(node, predicate, errorfmt):
346 def do_diskcheck_rcs(node, name):
348 rcs_dir = node.rcs_dir
349 except AttributeError:
350 if node.entry_exists_on_disk('RCS'):
351 rcs_dir = node.Dir('RCS')
354 node.rcs_dir = rcs_dir
356 return rcs_dir.entry_exists_on_disk(name+',v')
359 def ignore_diskcheck_rcs(node, name):
362 def do_diskcheck_sccs(node, name):
364 sccs_dir = node.sccs_dir
365 except AttributeError:
366 if node.entry_exists_on_disk('SCCS'):
367 sccs_dir = node.Dir('SCCS')
370 node.sccs_dir = sccs_dir
372 return sccs_dir.entry_exists_on_disk('s.'+name)
375 def ignore_diskcheck_sccs(node, name):
378 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
379 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
380 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
388 def set_diskcheck(list):
389 for dc in diskcheckers:
392 def diskcheck_types():
393 return map(lambda dc: dc.type, diskcheckers)
397 class EntryProxy(SCons.Util.Proxy):
398 def __get_abspath(self):
400 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
401 entry.name + "_abspath")
403 def __get_filebase(self):
404 name = self.get().name
405 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
408 def __get_suffix(self):
409 name = self.get().name
410 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
413 def __get_file(self):
414 name = self.get().name
415 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
417 def __get_base_path(self):
418 """Return the file's directory and file name, with the
421 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
422 entry.name + "_base")
424 def __get_posix_path(self):
425 """Return the path with / as the path separator,
426 regardless of platform."""
431 r = string.replace(entry.get_path(), os.sep, '/')
432 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
434 def __get_windows_path(self):
435 """Return the path with \ as the path separator,
436 regardless of platform."""
441 r = string.replace(entry.get_path(), os.sep, '\\')
442 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
444 def __get_srcnode(self):
445 return EntryProxy(self.get().srcnode())
447 def __get_srcdir(self):
448 """Returns the directory containing the source node linked to this
449 node via VariantDir(), or the directory of this node if not linked."""
450 return EntryProxy(self.get().srcnode().dir)
452 def __get_rsrcnode(self):
453 return EntryProxy(self.get().srcnode().rfile())
455 def __get_rsrcdir(self):
456 """Returns the directory containing the source node linked to this
457 node via VariantDir(), or the directory of this node if not linked."""
458 return EntryProxy(self.get().srcnode().rfile().dir)
461 return EntryProxy(self.get().dir)
463 dictSpecialAttrs = { "base" : __get_base_path,
464 "posix" : __get_posix_path,
465 "windows" : __get_windows_path,
466 "win32" : __get_windows_path,
467 "srcpath" : __get_srcnode,
468 "srcdir" : __get_srcdir,
470 "abspath" : __get_abspath,
471 "filebase" : __get_filebase,
472 "suffix" : __get_suffix,
474 "rsrcpath" : __get_rsrcnode,
475 "rsrcdir" : __get_rsrcdir,
478 def __getattr__(self, name):
479 # This is how we implement the "special" attributes
480 # such as base, posix, srcdir, etc.
482 attr_function = self.dictSpecialAttrs[name]
485 attr = SCons.Util.Proxy.__getattr__(self, name)
486 except AttributeError:
488 classname = string.split(str(entry.__class__), '.')[-1]
489 if classname[-2:] == "'>":
490 # new-style classes report their name as:
491 # "<class 'something'>"
492 # instead of the classic classes:
494 classname = classname[:-2]
495 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
498 return attr_function(self)
500 class Base(SCons.Node.Node):
501 """A generic class for file system entries. This class is for
502 when we don't know yet whether the entry being looked up is a file
503 or a directory. Instances of this class can morph into either
504 Dir or File objects by a later, more precise lookup.
506 Note: this class does not define __cmp__ and __hash__ for
507 efficiency reasons. SCons does a lot of comparing of
508 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
509 as fast as possible, which means we want to use Python's built-in
510 object identity comparisons.
513 memoizer_counters = []
515 def __init__(self, name, directory, fs):
516 """Initialize a generic Node.FS.Base object.
518 Call the superclass initialization, take care of setting up
519 our relative and absolute paths, identify our parent
520 directory, and indicate that this node should use
522 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
523 SCons.Node.Node.__init__(self)
526 self.suffix = SCons.Util.splitext(name)[1]
529 assert directory, "A directory must be provided"
531 self.abspath = directory.entry_abspath(name)
532 self.labspath = directory.entry_labspath(name)
533 if directory.path == '.':
536 self.path = directory.entry_path(name)
537 if directory.tpath == '.':
540 self.tpath = directory.entry_tpath(name)
541 self.path_elements = directory.path_elements + [self]
544 self.cwd = None # will hold the SConscript directory for target nodes
545 self.duplicate = directory.duplicate
547 def must_be_same(self, klass):
549 This node, which already existed, is being looked up as the
550 specified klass. Raise an exception if it isn't.
552 if self.__class__ is klass or klass is Entry:
554 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
555 (self.__class__.__name__, self.path, klass.__name__)
560 def get_suffix(self):
567 """A Node.FS.Base object's string representation is its path
571 return self._save_str()
572 return self._get_str()
574 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
578 return self._memo['_save_str']
581 result = self._get_str()
582 self._memo['_save_str'] = result
587 if self.duplicate or self.is_derived():
588 return self.get_path()
589 srcnode = self.srcnode()
590 if srcnode.stat() is None and not self.stat() is None:
591 result = self.get_path()
593 result = srcnode.get_path()
595 # We're not at the point where we're saving the string string
596 # representations of FS Nodes (because we haven't finished
597 # reading the SConscript files and need to have str() return
598 # things relative to them). That also means we can't yet
599 # cache values returned (or not returned) by stat(), since
600 # Python code in the SConscript files might still create
601 # or otherwise affect the on-disk file. So get rid of the
602 # values that the underlying stat() method saved.
603 try: del self._memo['stat']
604 except KeyError: pass
605 if not self is srcnode:
606 try: del srcnode._memo['stat']
607 except KeyError: pass
612 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
615 try: return self._memo['stat']
616 except KeyError: pass
617 try: result = self.fs.stat(self.abspath)
618 except os.error: result = None
619 self._memo['stat'] = result
623 return not self.stat() is None
626 return self.rfile().exists()
630 if st: return st[stat.ST_MTIME]
635 if st: return st[stat.ST_SIZE]
640 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
644 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
646 if hasattr(os, 'symlink'):
648 try: st = self.fs.lstat(self.abspath)
649 except os.error: return 0
650 return stat.S_ISLNK(st[stat.ST_MODE])
653 return 0 # no symlinks
655 def is_under(self, dir):
659 return self.dir.is_under(dir)
665 """If this node is in a build path, return the node
666 corresponding to its source file. Otherwise, return
669 srcdir_list = self.dir.srcdir_list()
671 srcnode = srcdir_list[0].Entry(self.name)
672 srcnode.must_be_same(self.__class__)
676 def get_path(self, dir=None):
677 """Return path relative to the current working directory of the
678 Node.FS.Base object that owns us."""
680 dir = self.fs.getcwd()
683 path_elems = self.path_elements
684 try: i = path_elems.index(dir)
685 except ValueError: pass
686 else: path_elems = path_elems[i+1:]
687 path_elems = map(lambda n: n.name, path_elems)
688 return string.join(path_elems, os.sep)
690 def set_src_builder(self, builder):
691 """Set the source code builder for this node."""
692 self.sbuilder = builder
693 if not self.has_builder():
694 self.builder_set(builder)
696 def src_builder(self):
697 """Fetch the source code builder for this node.
699 If there isn't one, we cache the source code builder specified
700 for the directory (which in turn will cache the value from its
701 parent directory, and so on up to the file system root).
705 except AttributeError:
706 scb = self.dir.src_builder()
710 def get_abspath(self):
711 """Get the absolute path of the file."""
714 def for_signature(self):
715 # Return just our name. Even an absolute path would not work,
716 # because that can change thanks to symlinks or remapped network
720 def get_subst_proxy(self):
723 except AttributeError:
724 ret = EntryProxy(self)
728 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
731 Generates a target entry that corresponds to this entry (usually
732 a source file) with the specified prefix and suffix.
734 Note that this method can be overridden dynamically for generated
735 files that need different behavior. See Tool/swig.py for
738 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
740 def _Rfindalldirs_key(self, pathlist):
743 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
745 def Rfindalldirs(self, pathlist):
747 Return all of the directories for a given path list, including
748 corresponding "backing" directories in any repositories.
750 The Node lookups are relative to this Node (typically a
751 directory), so memoizing result saves cycles from looking
752 up the same path for each target in a given directory.
755 memo_dict = self._memo['Rfindalldirs']
758 self._memo['Rfindalldirs'] = memo_dict
761 return memo_dict[pathlist]
765 create_dir_relative_to_self = self.Dir
767 for path in pathlist:
768 if isinstance(path, SCons.Node.Node):
771 dir = create_dir_relative_to_self(path)
772 result.extend(dir.get_all_rdirs())
774 memo_dict[pathlist] = result
778 def RDirs(self, pathlist):
779 """Search for a list of directories in the Repository list."""
780 cwd = self.cwd or self.fs._cwd
781 return cwd.Rfindalldirs(pathlist)
783 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
787 return self._memo['rentry']
791 if not self.exists():
792 norm_name = _my_normcase(self.name)
793 for dir in self.dir.get_all_rdirs():
795 node = dir.entries[norm_name]
797 if dir.entry_exists_on_disk(self.name):
798 result = dir.Entry(self.name)
800 self._memo['rentry'] = result
803 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
807 """This is the class for generic Node.FS entries--that is, things
808 that could be a File or a Dir, but we're just not sure yet.
809 Consequently, the methods in this class really exist just to
810 transform their associated object into the right class when the
811 time comes, and then call the same-named method in the transformed
814 def diskcheck_match(self):
817 def disambiguate(self, must_exist=None):
824 self.__class__ = File
828 # There was nothing on-disk at this location, so look in
831 # We can't just use self.srcnode() straight away because
832 # that would create an actual Node for this file in the src
833 # directory, and there might not be one. Instead, use the
834 # dir_on_disk() method to see if there's something on-disk
835 # with that name, in which case we can go ahead and call
836 # self.srcnode() to create the right type of entry.
837 srcdir = self.dir.srcnode()
838 if srcdir != self.dir and \
839 srcdir.entry_exists_on_disk(self.name) and \
840 self.srcnode().isdir():
844 msg = "No such file or directory: '%s'" % self.abspath
845 raise SCons.Errors.UserError, msg
847 self.__class__ = File
853 """We're a generic Entry, but the caller is actually looking for
854 a File at this point, so morph into one."""
855 self.__class__ = File
858 return File.rfile(self)
860 def scanner_key(self):
861 return self.get_suffix()
863 def get_contents(self):
864 """Fetch the contents of the entry.
866 Since this should return the real contents from the file
867 system, we check to see into what sort of subclass we should
870 self = self.disambiguate(must_exist=1)
871 except SCons.Errors.UserError:
872 # There was nothing on disk with which to disambiguate
873 # this entry. Leave it as an Entry, but return a null
874 # string so calls to get_contents() in emitters and the
875 # like (e.g. in qt.py) don't have to disambiguate by hand
876 # or catch the exception.
879 return self.get_contents()
881 def must_be_same(self, klass):
882 """Called to make sure a Node is a Dir. Since we're an
883 Entry, we can morph into one."""
884 if not self.__class__ is klass:
885 self.__class__ = klass
889 # The following methods can get called before the Taskmaster has
890 # had a chance to call disambiguate() directly to see if this Entry
891 # should really be a Dir or a File. We therefore use these to call
892 # disambiguate() transparently (from our caller's point of view).
894 # Right now, this minimal set of methods has been derived by just
895 # looking at some of the methods that will obviously be called early
896 # in any of the various Taskmasters' calling sequences, and then
897 # empirically figuring out which additional methods are necessary
898 # to make various tests pass.
901 """Return if the Entry exists. Check the file system to see
902 what we should turn into first. Assume a file if there's no
904 return self.disambiguate().exists()
906 def rel_path(self, other):
907 d = self.disambiguate()
908 if d.__class__ == Entry:
909 raise "rel_path() could not disambiguate File/Dir"
910 return d.rel_path(other)
913 return self.disambiguate().new_ninfo()
915 def changed_since_last_build(self, target, prev_ni):
916 return self.disambiguate().changed_since_last_build(target, prev_ni)
918 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
919 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
921 # This is for later so we can differentiate between Entry the class and Entry
922 # the method of the FS class.
928 if SCons.Memoize.use_memoizer:
929 __metaclass__ = SCons.Memoize.Memoized_Metaclass
931 # This class implements an abstraction layer for operations involving
932 # a local file system. Essentially, this wraps any function in
933 # the os, os.path or shutil modules that we use to actually go do
934 # anything with or to the local file system.
936 # Note that there's a very good chance we'll refactor this part of
937 # the architecture in some way as we really implement the interface(s)
938 # for remote file system Nodes. For example, the right architecture
939 # might be to have this be a subclass instead of a base class.
940 # Nevertheless, we're using this as a first step in that direction.
942 # We're not using chdir() yet because the calling subclass method
943 # needs to use os.chdir() directly to avoid recursion. Will we
944 # really need this one?
945 #def chdir(self, path):
946 # return os.chdir(path)
947 def chmod(self, path, mode):
948 return os.chmod(path, mode)
949 def copy(self, src, dst):
950 return shutil.copy(src, dst)
951 def copy2(self, src, dst):
952 return shutil.copy2(src, dst)
953 def exists(self, path):
954 return os.path.exists(path)
955 def getmtime(self, path):
956 return os.path.getmtime(path)
957 def getsize(self, path):
958 return os.path.getsize(path)
959 def isdir(self, path):
960 return os.path.isdir(path)
961 def isfile(self, path):
962 return os.path.isfile(path)
963 def link(self, src, dst):
964 return os.link(src, dst)
965 def lstat(self, path):
966 return os.lstat(path)
967 def listdir(self, path):
968 return os.listdir(path)
969 def makedirs(self, path):
970 return os.makedirs(path)
971 def mkdir(self, path):
972 return os.mkdir(path)
973 def rename(self, old, new):
974 return os.rename(old, new)
975 def stat(self, path):
977 def symlink(self, src, dst):
978 return os.symlink(src, dst)
979 def open(self, path):
981 def unlink(self, path):
982 return os.unlink(path)
984 if hasattr(os, 'symlink'):
985 def islink(self, path):
986 return os.path.islink(path)
988 def islink(self, path):
989 return 0 # no symlinks
991 if hasattr(os, 'readlink'):
992 def readlink(self, file):
993 return os.readlink(file)
995 def readlink(self, file):
1000 # # Skeleton for the obvious methods we might need from the
1001 # # abstraction layer for a remote filesystem.
1002 # def upload(self, local_src, remote_dst):
1004 # def download(self, remote_src, local_dst):
1010 memoizer_counters = []
1012 def __init__(self, path = None):
1013 """Initialize the Node.FS subsystem.
1015 The supplied path is the top of the source tree, where we
1016 expect to find the top-level build file. If no path is
1017 supplied, the current directory is the default.
1019 The path argument must be a valid absolute path.
1021 if __debug__: logInstanceCreation(self, 'Node.FS')
1026 self.SConstruct_dir = None
1027 self.max_drift = default_max_drift
1031 self.pathTop = os.getcwd()
1034 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1036 self.Top = self.Dir(self.pathTop)
1038 self.Top.tpath = '.'
1039 self._cwd = self.Top
1041 DirNodeInfo.fs = self
1042 FileNodeInfo.fs = self
1044 def set_SConstruct_dir(self, dir):
1045 self.SConstruct_dir = dir
1047 def get_max_drift(self):
1048 return self.max_drift
1050 def set_max_drift(self, max_drift):
1051 self.max_drift = max_drift
1056 def chdir(self, dir, change_os_dir=0):
1057 """Change the current working directory for lookups.
1058 If change_os_dir is true, we will also change the "real" cwd
1066 os.chdir(dir.abspath)
1071 def get_root(self, drive):
1073 Returns the root directory for the specified drive, creating
1076 drive = _my_normcase(drive)
1078 return self.Root[drive]
1080 root = RootDir(drive, self)
1081 self.Root[drive] = root
1083 self.Root[self.defaultDrive] = root
1084 elif drive == self.defaultDrive:
1085 self.Root[''] = root
1088 def _lookup(self, p, directory, fsclass, create=1):
1090 The generic entry point for Node lookup with user-supplied data.
1092 This translates arbitrary input into a canonical Node.FS object
1093 of the specified fsclass. The general approach for strings is
1094 to turn it into a fully normalized absolute path and then call
1095 the root directory's lookup_abs() method for the heavy lifting.
1097 If the path name begins with '#', it is unconditionally
1098 interpreted relative to the top-level directory of this FS. '#'
1099 is treated as a synonym for the top-level SConstruct directory,
1100 much like '~' is treated as a synonym for the user's home
1101 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1102 to the 'foo' subdirectory underneath the top-level SConstruct
1105 If the path name is relative, then the path is looked up relative
1106 to the specified directory, or the current directory (self._cwd,
1107 typically the SConscript directory) if the specified directory
1110 if isinstance(p, Base):
1111 # It's already a Node.FS object. Make sure it's the right
1113 p.must_be_same(fsclass)
1115 # str(p) in case it's something like a proxy object
1118 initial_hash = (p[0:1] == '#')
1120 # There was an initial '#', so we strip it and override
1121 # whatever directory they may have specified with the
1122 # top-level SConstruct directory.
1124 directory = self.Top
1126 if directory and not isinstance(directory, Dir):
1127 directory = self.Dir(directory)
1130 drive, p = os.path.splitdrive(p)
1134 # This causes a naked drive letter to be treated as a synonym
1135 # for the root directory on that drive.
1137 absolute = os.path.isabs(p)
1139 needs_normpath = needs_normpath_check.match(p)
1141 if initial_hash or not absolute:
1142 # This is a relative lookup, either to the top-level
1143 # SConstruct directory (because of the initial '#') or to
1144 # the current directory (the path name is not absolute).
1145 # Add the string to the appropriate directory lookup path,
1146 # after which the whole thing gets normalized.
1148 directory = self._cwd
1150 p = directory.labspath + '/' + p
1152 p = directory.labspath
1155 p = os.path.normpath(p)
1157 if drive or absolute:
1158 root = self.get_root(drive)
1161 directory = self._cwd
1162 root = directory.root
1165 p = string.replace(p, os.sep, '/')
1166 return root._lookup_abs(p, fsclass, create)
1168 def Entry(self, name, directory = None, create = 1):
1169 """Lookup or create a generic Entry node with the specified name.
1170 If the name is a relative path (begins with ./, ../, or a file
1171 name), then it is looked up relative to the supplied directory
1172 node, or to the top level directory of the FS (supplied at
1173 construction time) if no directory is supplied.
1175 return self._lookup(name, directory, Entry, create)
1177 def File(self, name, directory = None, create = 1):
1178 """Lookup or create a File node with the specified name. If
1179 the name is a relative path (begins with ./, ../, or a file name),
1180 then it is looked up relative to the supplied directory node,
1181 or to the top level directory of the FS (supplied at construction
1182 time) if no directory is supplied.
1184 This method will raise TypeError if a directory is found at the
1187 return self._lookup(name, directory, File, create)
1189 def Dir(self, name, directory = None, create = True):
1190 """Lookup or create a Dir node with the specified name. If
1191 the name is a relative path (begins with ./, ../, or a file name),
1192 then it is looked up relative to the supplied directory node,
1193 or to the top level directory of the FS (supplied at construction
1194 time) if no directory is supplied.
1196 This method will raise TypeError if a normal file is found at the
1199 return self._lookup(name, directory, Dir, create)
1201 def VariantDir(self, variant_dir, src_dir, duplicate=1):
1202 """Link the supplied variant directory to the source directory
1203 for purposes of building files."""
1205 if not isinstance(src_dir, SCons.Node.Node):
1206 src_dir = self.Dir(src_dir)
1207 if not isinstance(variant_dir, SCons.Node.Node):
1208 variant_dir = self.Dir(variant_dir)
1209 if src_dir.is_under(variant_dir):
1210 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1211 if variant_dir.srcdir:
1212 if variant_dir.srcdir == src_dir:
1213 return # We already did this.
1214 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1215 variant_dir.link(src_dir, duplicate)
1217 def Repository(self, *dirs):
1218 """Specify Repository directories to search."""
1220 if not isinstance(d, SCons.Node.Node):
1222 self.Top.addRepository(d)
1224 def variant_dir_target_climb(self, orig, dir, tail):
1225 """Create targets in corresponding variant directories
1227 Climb the directory tree, and look up path names
1228 relative to any linked variant directories we find.
1230 Even though this loops and walks up the tree, we don't memoize
1231 the return value because this is really only used to process
1232 the command-line targets.
1236 fmt = "building associated VariantDir targets: %s"
1239 for bd in dir.variant_dirs:
1240 if start_dir.is_under(bd):
1241 # If already in the build-dir location, don't reflect
1242 return [orig], fmt % str(orig)
1243 p = apply(os.path.join, [bd.path] + tail)
1244 targets.append(self.Entry(p))
1245 tail = [dir.name] + tail
1248 message = fmt % string.join(map(str, targets))
1249 return targets, message
1251 def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1255 This is mainly a shim layer
1259 return cwd.glob(pathname, ondisk, source, strings)
1261 class DirNodeInfo(SCons.Node.NodeInfoBase):
1262 # This should get reset by the FS initialization.
1263 current_version_id = 1
1267 def str_to_node(self, s):
1271 drive, s = os.path.splitdrive(s)
1273 root = self.fs.get_root(drive)
1274 if not os.path.isabs(s):
1275 s = top.labspath + '/' + s
1276 return root._lookup_abs(s, Entry)
1278 class DirBuildInfo(SCons.Node.BuildInfoBase):
1279 current_version_id = 1
1281 glob_magic_check = re.compile('[*?[]')
1283 def has_glob_magic(s):
1284 return glob_magic_check.search(s) is not None
1287 """A class for directories in a file system.
1290 memoizer_counters = []
1292 NodeInfo = DirNodeInfo
1293 BuildInfo = DirBuildInfo
1295 def __init__(self, name, directory, fs):
1296 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1297 Base.__init__(self, name, directory, fs)
1301 """Turn a file system Node (either a freshly initialized directory
1302 object or a separate Entry object) into a proper directory object.
1304 Set up this directory's entries and hook it into the file
1305 system tree. Specify that directories (this Node) don't use
1306 signatures for calculating whether they're current.
1309 self.repositories = []
1313 self.entries['.'] = self
1314 self.entries['..'] = self.dir
1317 self._sconsign = None
1318 self.variant_dirs = []
1319 self.root = self.dir.root
1321 # Don't just reset the executor, replace its action list,
1322 # because it might have some pre-or post-actions that need to
1324 self.builder = get_MkdirBuilder()
1325 self.get_executor().set_action_list(self.builder.action)
1327 def diskcheck_match(self):
1328 diskcheck_match(self, self.isfile,
1329 "File %s found where directory expected.")
1331 def __clearRepositoryCache(self, duplicate=None):
1332 """Called when we change the repository(ies) for a directory.
1333 This clears any cached information that is invalidated by changing
1336 for node in self.entries.values():
1337 if node != self.dir:
1338 if node != self and isinstance(node, Dir):
1339 node.__clearRepositoryCache(duplicate)
1344 except AttributeError:
1346 if duplicate != None:
1347 node.duplicate=duplicate
1349 def __resetDuplicate(self, node):
1351 node.duplicate = node.get_dir().duplicate
1353 def Entry(self, name):
1355 Looks up or creates an entry node named 'name' relative to
1358 return self.fs.Entry(name, self)
1360 def Dir(self, name, create=True):
1362 Looks up or creates a directory node named 'name' relative to
1365 dir = self.fs.Dir(name, self, create)
1368 def File(self, name):
1370 Looks up or creates a file node named 'name' relative to
1373 return self.fs.File(name, self)
1375 def _lookup_rel(self, name, klass, create=1):
1377 Looks up a *normalized* relative path name, relative to this
1380 This method is intended for use by internal lookups with
1381 already-normalized path data. For general-purpose lookups,
1382 use the Entry(), Dir() and File() methods above.
1384 This method does *no* input checking and will die or give
1385 incorrect results if it's passed a non-normalized path name (e.g.,
1386 a path containing '..'), an absolute path name, a top-relative
1387 ('#foo') path name, or any kind of object.
1389 name = self.entry_labspath(name)
1390 return self.root._lookup_abs(name, klass, create)
1392 def link(self, srcdir, duplicate):
1393 """Set this directory as the variant directory for the
1394 supplied source directory."""
1395 self.srcdir = srcdir
1396 self.duplicate = duplicate
1397 self.__clearRepositoryCache(duplicate)
1398 srcdir.variant_dirs.append(self)
1400 def getRepositories(self):
1401 """Returns a list of repositories for this directory.
1403 if self.srcdir and not self.duplicate:
1404 return self.srcdir.get_all_rdirs() + self.repositories
1405 return self.repositories
1407 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1409 def get_all_rdirs(self):
1411 return self._memo['get_all_rdirs']
1419 for rep in dir.getRepositories():
1420 result.append(rep.Dir(fname))
1424 fname = dir.name + os.sep + fname
1427 self._memo['get_all_rdirs'] = result
1431 def addRepository(self, dir):
1432 if dir != self and not dir in self.repositories:
1433 self.repositories.append(dir)
1435 self.__clearRepositoryCache()
1438 return self.entries['..']
1440 def _rel_path_key(self, other):
1443 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1445 def rel_path(self, other):
1446 """Return a path to "other" relative to this directory.
1449 # This complicated and expensive method, which constructs relative
1450 # paths between arbitrary Node.FS objects, is no longer used
1451 # by SCons itself. It was introduced to store dependency paths
1452 # in .sconsign files relative to the target, but that ended up
1453 # being significantly inefficient.
1455 # We're continuing to support the method because some SConstruct
1456 # files out there started using it when it was available, and
1457 # we're all about backwards compatibility..
1460 memo_dict = self._memo['rel_path']
1463 self._memo['rel_path'] = memo_dict
1466 return memo_dict[other]
1474 elif not other in self.path_elements:
1477 other_dir = other.get_dir()
1478 except AttributeError:
1481 if other_dir is None:
1484 dir_rel_path = self.rel_path(other_dir)
1485 if dir_rel_path == '.':
1488 result = dir_rel_path + os.sep + other.name
1492 i = self.path_elements.index(other) + 1
1494 path_elems = ['..'] * (len(self.path_elements) - i) \
1495 + map(lambda n: n.name, other.path_elements[i:])
1497 result = string.join(path_elems, os.sep)
1499 memo_dict[other] = result
1503 def get_env_scanner(self, env, kw={}):
1504 import SCons.Defaults
1505 return SCons.Defaults.DirEntryScanner
1507 def get_target_scanner(self):
1508 import SCons.Defaults
1509 return SCons.Defaults.DirEntryScanner
1511 def get_found_includes(self, env, scanner, path):
1512 """Return this directory's implicit dependencies.
1514 We don't bother caching the results because the scan typically
1515 shouldn't be requested more than once (as opposed to scanning
1516 .h file contents, which can be requested as many times as the
1517 files is #included by other files).
1521 # Clear cached info for this Dir. If we already visited this
1522 # directory on our walk down the tree (because we didn't know at
1523 # that point it was being used as the source for another Node)
1524 # then we may have calculated build signature before realizing
1525 # we had to scan the disk. Now that we have to, though, we need
1526 # to invalidate the old calculated signature so that any node
1527 # dependent on our directory structure gets one that includes
1528 # info about everything on disk.
1530 return scanner(self, env, path)
1533 # Taskmaster interface subsystem
1539 def build(self, **kw):
1540 """A null "builder" for directories."""
1542 if not self.builder is MkdirBuilder:
1543 apply(SCons.Node.Node.build, [self,], kw)
1550 """Create this directory, silently and without worrying about
1551 whether the builder is the default or not."""
1557 listDirs.append(parent)
1560 raise SCons.Errors.StopError, parent.path
1563 for dirnode in listDirs:
1565 # Don't call dirnode.build(), call the base Node method
1566 # directly because we definitely *must* create this
1567 # directory. The dirnode.build() method will suppress
1568 # the build if it's the default builder.
1569 SCons.Node.Node.build(dirnode)
1570 dirnode.get_executor().nullify()
1571 # The build() action may or may not have actually
1572 # created the directory, depending on whether the -n
1573 # option was used or not. Delete the _exists and
1574 # _rexists attributes so they can be reevaluated.
1579 def multiple_side_effect_has_builder(self):
1581 return not self.builder is MkdirBuilder and self.has_builder()
1583 def alter_targets(self):
1584 """Return any corresponding targets in a variant directory.
1586 return self.fs.variant_dir_target_climb(self, self, [])
1588 def scanner_key(self):
1589 """A directory does not get scanned."""
1592 def get_contents(self):
1593 """Return aggregate contents of all our children."""
1594 contents = map(lambda n: n.get_contents(), self.children())
1595 return string.join(contents, '')
1597 def do_duplicate(self, src):
1600 changed_since_last_build = SCons.Node.Node.state_has_changed
1602 def is_up_to_date(self):
1603 """If any child is not up-to-date, then this directory isn't,
1605 if not self.builder is MkdirBuilder and not self.exists():
1607 up_to_date = SCons.Node.up_to_date
1608 for kid in self.children():
1609 if kid.get_state() > up_to_date:
1614 if not self.exists():
1615 norm_name = _my_normcase(self.name)
1616 for dir in self.dir.get_all_rdirs():
1617 try: node = dir.entries[norm_name]
1618 except KeyError: node = dir.dir_on_disk(self.name)
1619 if node and node.exists() and \
1620 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1625 """Return the .sconsign file info for this directory,
1626 creating it first if necessary."""
1627 if not self._sconsign:
1628 import SCons.SConsign
1629 self._sconsign = SCons.SConsign.ForDirectory(self)
1630 return self._sconsign
1633 """Dir has a special need for srcnode()...if we
1634 have a srcdir attribute set, then that *is* our srcnode."""
1637 return Base.srcnode(self)
1639 def get_timestamp(self):
1640 """Return the latest timestamp from among our children"""
1642 for kid in self.children():
1643 if kid.get_timestamp() > stamp:
1644 stamp = kid.get_timestamp()
1647 def entry_abspath(self, name):
1648 return self.abspath + os.sep + name
1650 def entry_labspath(self, name):
1651 return self.labspath + '/' + name
1653 def entry_path(self, name):
1654 return self.path + os.sep + name
1656 def entry_tpath(self, name):
1657 return self.tpath + os.sep + name
1659 def entry_exists_on_disk(self, name):
1661 d = self.on_disk_entries
1662 except AttributeError:
1665 entries = os.listdir(self.abspath)
1669 for entry in map(_my_normcase, entries):
1671 self.on_disk_entries = d
1672 return d.has_key(_my_normcase(name))
1674 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1676 def srcdir_list(self):
1678 return self._memo['srcdir_list']
1688 result.append(dir.srcdir.Dir(dirname))
1689 dirname = dir.name + os.sep + dirname
1692 self._memo['srcdir_list'] = result
1696 def srcdir_duplicate(self, name):
1697 for dir in self.srcdir_list():
1698 if self.is_under(dir):
1699 # We shouldn't source from something in the build path;
1700 # variant_dir is probably under src_dir, in which case
1701 # we are reflecting.
1703 if dir.entry_exists_on_disk(name):
1704 srcnode = dir.Entry(name).disambiguate()
1706 node = self.Entry(name).disambiguate()
1707 node.do_duplicate(srcnode)
1713 def _srcdir_find_file_key(self, filename):
1716 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1718 def srcdir_find_file(self, filename):
1720 memo_dict = self._memo['srcdir_find_file']
1723 self._memo['srcdir_find_file'] = memo_dict
1726 return memo_dict[filename]
1731 if (isinstance(node, File) or isinstance(node, Entry)) and \
1732 (node.is_derived() or node.exists()):
1736 norm_name = _my_normcase(filename)
1738 for rdir in self.get_all_rdirs():
1739 try: node = rdir.entries[norm_name]
1740 except KeyError: node = rdir.file_on_disk(filename)
1741 else: node = func(node)
1743 result = (node, self)
1744 memo_dict[filename] = result
1747 for srcdir in self.srcdir_list():
1748 for rdir in srcdir.get_all_rdirs():
1749 try: node = rdir.entries[norm_name]
1750 except KeyError: node = rdir.file_on_disk(filename)
1751 else: node = func(node)
1753 result = (File(filename, self, self.fs), srcdir)
1754 memo_dict[filename] = result
1757 result = (None, None)
1758 memo_dict[filename] = result
1761 def dir_on_disk(self, name):
1762 if self.entry_exists_on_disk(name):
1763 try: return self.Dir(name)
1764 except TypeError: pass
1767 def file_on_disk(self, name):
1768 if self.entry_exists_on_disk(name) or \
1769 diskcheck_rcs(self, name) or \
1770 diskcheck_sccs(self, name):
1771 try: return self.File(name)
1772 except TypeError: pass
1773 node = self.srcdir_duplicate(name)
1774 if isinstance(node, Dir):
1778 def walk(self, func, arg):
1780 Walk this directory tree by calling the specified function
1781 for each directory in the tree.
1783 This behaves like the os.path.walk() function, but for in-memory
1784 Node.FS.Dir objects. The function takes the same arguments as
1785 the functions passed to os.path.walk():
1787 func(arg, dirname, fnames)
1789 Except that "dirname" will actually be the directory *Node*,
1790 not the string. The '.' and '..' entries are excluded from
1791 fnames. The fnames list may be modified in-place to filter the
1792 subdirectories visited or otherwise impose a specific order.
1793 The "arg" argument is always passed to func() and may be used
1794 in any way (or ignored, passing None is common).
1796 entries = self.entries
1797 names = entries.keys()
1800 func(arg, self, names)
1801 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1802 for dirname in filter(select_dirs, names):
1803 entries[dirname].walk(func, arg)
1805 def glob(self, pathname, ondisk=True, source=False, strings=False):
1807 Returns a list of Nodes (or strings) matching a specified
1810 Pathname patterns follow UNIX shell semantics: * matches
1811 any-length strings of any characters, ? matches any character,
1812 and [] can enclose lists or ranges of characters. Matches do
1813 not span directory separators.
1815 The matches take into account Repositories, returning local
1816 Nodes if a corresponding entry exists in a Repository (either
1817 an in-memory Node or something on disk).
1819 By defafult, the glob() function matches entries that exist
1820 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1821 argument to False (or some other non-true value) causes the glob()
1822 function to only match in-memory Nodes. The default behavior is
1823 to return both the on-disk and in-memory Nodes.
1825 The "source" argument, when true, specifies that corresponding
1826 source Nodes must be returned if you're globbing in a build
1827 directory (initialized with VariantDir()). The default behavior
1828 is to return Nodes local to the VariantDir().
1830 The "strings" argument, when true, returns the matches as strings,
1831 not Nodes. The strings are path names relative to this directory.
1833 The underlying algorithm is adapted from the glob.glob() function
1834 in the Python library (but heavily modified), and uses fnmatch()
1837 dirname, basename = os.path.split(pathname)
1839 return self._glob1(basename, ondisk, source, strings)
1840 if has_glob_magic(dirname):
1841 list = self.glob(dirname, ondisk, source, strings=False)
1843 list = [self.Dir(dirname, create=True)]
1846 r = dir._glob1(basename, ondisk, source, strings)
1848 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1852 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1854 Globs for and returns a list of entry names matching a single
1855 pattern in this directory.
1857 This searches any repositories and source directories for
1858 corresponding entries and returns a Node (or string) relative
1859 to the current directory if an entry is found anywhere.
1861 TODO: handle pattern with no wildcard
1863 search_dir_list = self.get_all_rdirs()
1864 for srcdir in self.srcdir_list():
1865 search_dir_list.extend(srcdir.get_all_rdirs())
1868 for dir in search_dir_list:
1869 # We use the .name attribute from the Node because the keys of
1870 # the dir.entries dictionary are normalized (that is, all upper
1871 # case) on case-insensitive systems like Windows.
1872 #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
1873 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1874 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1875 names.extend(node_names)
1878 disk_names = os.listdir(dir.abspath)
1882 names.extend(disk_names)
1884 # We're going to return corresponding Nodes in
1885 # the local directory, so we need to make sure
1886 # those Nodes exist. We only want to create
1887 # Nodes for the entries that will match the
1888 # specified pattern, though, which means we
1889 # need to filter the list here, even though
1890 # the overall list will also be filtered later,
1891 # after we exit this loop.
1892 if pattern[0] != '.':
1893 #disk_names = [ d for d in disk_names if d[0] != '.' ]
1894 disk_names = filter(lambda x: x[0] != '.', disk_names)
1895 disk_names = fnmatch.filter(disk_names, pattern)
1896 rep_nodes = map(dir.Entry, disk_names)
1897 #rep_nodes = [ n.disambiguate() for n in rep_nodes ]
1898 rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
1899 for node, name in izip(rep_nodes, disk_names):
1900 n = self.Entry(name)
1901 if n.__class__ != node.__class__:
1902 n.__class__ = node.__class__
1906 if pattern[0] != '.':
1907 #names = [ n for n in names if n[0] != '.' ]
1908 names = filter(lambda x: x[0] != '.', names)
1909 names = fnmatch.filter(names, pattern)
1914 #return [ self.entries[_my_normcase(n)] for n in names ]
1915 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1918 """A class for the root directory of a file system.
1920 This is the same as a Dir class, except that the path separator
1921 ('/' or '\\') is actually part of the name, so we don't need to
1922 add a separator when creating the path names of entries within
1925 def __init__(self, name, fs):
1926 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1927 # We're going to be our own parent directory (".." entry and .dir
1928 # attribute) so we have to set up some values so Base.__init__()
1929 # won't gag won't it calls some of our methods.
1934 self.path_elements = []
1937 Base.__init__(self, name, self, fs)
1939 # Now set our paths to what we really want them to be: the
1940 # initial drive letter (the name) plus the directory separator,
1941 # except for the "lookup abspath," which does not have the
1943 self.abspath = name + os.sep
1945 self.path = name + os.sep
1946 self.tpath = name + os.sep
1949 self._lookupDict = {}
1951 # The // and os.sep + os.sep entries are necessary because
1952 # os.path.normpath() seems to preserve double slashes at the
1953 # beginning of a path (presumably for UNC path names), but
1954 # collapses triple slashes to a single slash.
1955 self._lookupDict[''] = self
1956 self._lookupDict['/'] = self
1957 self._lookupDict['//'] = self
1958 self._lookupDict[os.sep] = self
1959 self._lookupDict[os.sep + os.sep] = self
1961 def must_be_same(self, klass):
1964 Base.must_be_same(self, klass)
1966 def _lookup_abs(self, p, klass, create=1):
1968 Fast (?) lookup of a *normalized* absolute path.
1970 This method is intended for use by internal lookups with
1971 already-normalized path data. For general-purpose lookups,
1972 use the FS.Entry(), FS.Dir() or FS.File() methods.
1974 The caller is responsible for making sure we're passed a
1975 normalized absolute path; we merely let Python's dictionary look
1976 up and return the One True Node.FS object for the path.
1978 If no Node for the specified "p" doesn't already exist, and
1979 "create" is specified, the Node may be created after recursive
1980 invocation to find or create the parent directory or directories.
1984 result = self._lookupDict[k]
1987 raise SCons.Errors.UserError
1988 # There is no Node for this path name, and we're allowed
1990 dir_name, file_name = os.path.split(p)
1991 dir_node = self._lookup_abs(dir_name, Dir)
1992 result = klass(file_name, dir_node, self.fs)
1993 self._lookupDict[k] = result
1994 dir_node.entries[_my_normcase(file_name)] = result
1995 dir_node.implicit = None
1997 # Double-check on disk (as configured) that the Node we
1998 # created matches whatever is out there in the real world.
1999 result.diskcheck_match()
2001 # There is already a Node for this path name. Allow it to
2002 # complain if we were looking for an inappropriate type.
2003 result.must_be_same(klass)
2009 def entry_abspath(self, name):
2010 return self.abspath + name
2012 def entry_labspath(self, name):
2015 def entry_path(self, name):
2016 return self.path + name
2018 def entry_tpath(self, name):
2019 return self.tpath + name
2021 def is_under(self, dir):
2033 def src_builder(self):
2036 class FileNodeInfo(SCons.Node.NodeInfoBase):
2037 current_version_id = 1
2039 field_list = ['csig', 'timestamp', 'size']
2041 # This should get reset by the FS initialization.
2044 def str_to_node(self, s):
2048 drive, s = os.path.splitdrive(s)
2050 root = self.fs.get_root(drive)
2051 if not os.path.isabs(s):
2052 s = top.labspath + '/' + s
2053 return root._lookup_abs(s, Entry)
2055 class FileBuildInfo(SCons.Node.BuildInfoBase):
2056 current_version_id = 1
2058 def convert_to_sconsign(self):
2060 Converts this FileBuildInfo object for writing to a .sconsign file
2062 This replaces each Node in our various dependency lists with its
2063 usual string representation: relative to the top-level SConstruct
2064 directory, or an absolute path if it's outside.
2072 except AttributeError:
2075 s = string.replace(s, os.sep, '/')
2077 for attr in ['bsources', 'bdepends', 'bimplicit']:
2079 val = getattr(self, attr)
2080 except AttributeError:
2083 setattr(self, attr, map(node_to_str, val))
2084 def convert_from_sconsign(self, dir, name):
2086 Converts a newly-read FileBuildInfo object for in-SCons use
2088 For normal up-to-date checking, we don't have any conversion to
2089 perform--but we're leaving this method here to make that clear.
2092 def prepare_dependencies(self):
2094 Prepares a FileBuildInfo object for explaining what changed
2096 The bsources, bdepends and bimplicit lists have all been
2097 stored on disk as paths relative to the top-level SConstruct
2098 directory. Convert the strings to actual Nodes (for use by the
2099 --debug=explain code and --implicit-cache).
2102 ('bsources', 'bsourcesigs'),
2103 ('bdepends', 'bdependsigs'),
2104 ('bimplicit', 'bimplicitsigs'),
2106 for (nattr, sattr) in attrs:
2108 strings = getattr(self, nattr)
2109 nodeinfos = getattr(self, sattr)
2110 except AttributeError:
2114 for s, ni in izip(strings, nodeinfos):
2115 if not isinstance(s, SCons.Node.Node):
2116 s = ni.str_to_node(s)
2118 setattr(self, nattr, nodes)
2119 def format(self, names=0):
2121 bkids = self.bsources + self.bdepends + self.bimplicit
2122 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
2123 for bkid, bkidsig in izip(bkids, bkidsigs):
2124 result.append(str(bkid) + ': ' +
2125 string.join(bkidsig.format(names=names), ' '))
2126 result.append('%s [%s]' % (self.bactsig, self.bact))
2127 return string.join(result, '\n')
2130 """A class for files in a file system.
2133 memoizer_counters = []
2135 NodeInfo = FileNodeInfo
2136 BuildInfo = FileBuildInfo
2138 def diskcheck_match(self):
2139 diskcheck_match(self, self.isdir,
2140 "Directory %s found where file expected.")
2142 def __init__(self, name, directory, fs):
2143 if __debug__: logInstanceCreation(self, 'Node.FS.File')
2144 Base.__init__(self, name, directory, fs)
2147 def Entry(self, name):
2148 """Create an entry node named 'name' relative to
2149 the SConscript directory of this file."""
2150 return self.cwd.Entry(name)
2152 def Dir(self, name, create=True):
2153 """Create a directory node named 'name' relative to
2154 the SConscript directory of this file."""
2155 return self.cwd.Dir(name, create)
2157 def Dirs(self, pathlist):
2158 """Create a list of directories relative to the SConscript
2159 directory of this file."""
2160 return map(lambda p, s=self: s.Dir(p), pathlist)
2162 def File(self, name):
2163 """Create a file node named 'name' relative to
2164 the SConscript directory of this file."""
2165 return self.cwd.File(name)
2167 #def generate_build_dict(self):
2168 # """Return an appropriate dictionary of values for building
2170 # return {'Dir' : self.Dir,
2171 # 'File' : self.File,
2172 # 'RDirs' : self.RDirs}
2175 """Turn a file system node into a File object."""
2176 self.scanner_paths = {}
2177 if not hasattr(self, '_local'):
2180 # If there was already a Builder set on this entry, then
2181 # we need to make sure we call the target-decider function,
2182 # not the source-decider. Reaching in and doing this by hand
2183 # is a little bogus. We'd prefer to handle this by adding
2184 # an Entry.builder_set() method that disambiguates like the
2185 # other methods, but that starts running into problems with the
2186 # fragile way we initialize Dir Nodes with their Mkdir builders,
2187 # yet still allow them to be overridden by the user. Since it's
2188 # not clear right now how to fix that, stick with what works
2189 # until it becomes clear...
2190 if self.has_builder():
2191 self.changed_since_last_build = self.decide_target
2193 def scanner_key(self):
2194 return self.get_suffix()
2196 def get_contents(self):
2197 if not self.rexists():
2199 fname = self.rfile().abspath
2201 r = open(fname, "rb").read()
2202 except EnvironmentError, e:
2208 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2212 return self._memo['get_size']
2217 size = self.rfile().getsize()
2221 self._memo['get_size'] = size
2225 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2227 def get_timestamp(self):
2229 return self._memo['get_timestamp']
2234 timestamp = self.rfile().getmtime()
2238 self._memo['get_timestamp'] = timestamp
2242 def store_info(self):
2243 # Merge our build information into the already-stored entry.
2244 # This accomodates "chained builds" where a file that's a target
2245 # in one build (SConstruct file) is a source in a different build.
2246 # See test/chained-build.py for the use case.
2247 self.dir.sconsign().store_info(self.name, self)
2249 convert_copy_attrs = [
2259 convert_sig_attrs = [
2265 def convert_old_entry(self, old_entry):
2266 # Convert a .sconsign entry from before the Big Signature
2267 # Refactoring, doing what we can to convert its information
2268 # to the new .sconsign entry format.
2270 # The old format looked essentially like this:
2279 # .bsourcesigs ("signature" list)
2281 # .bdependsigs ("signature" list)
2283 # .bimplicitsigs ("signature" list)
2287 # The new format looks like this:
2294 # .binfo (BuildInfo)
2296 # .bsourcesigs (NodeInfo list)
2302 # .bdependsigs (NodeInfo list)
2308 # .bimplicitsigs (NodeInfo list)
2316 # The basic idea of the new structure is that a NodeInfo always
2317 # holds all available information about the state of a given Node
2318 # at a certain point in time. The various .b*sigs lists can just
2319 # be a list of pointers to the .ninfo attributes of the different
2320 # dependent nodes, without any copying of information until it's
2321 # time to pickle it for writing out to a .sconsign file.
2323 # The complicating issue is that the *old* format only stored one
2324 # "signature" per dependency, based on however the *last* build
2325 # was configured. We don't know from just looking at it whether
2326 # it was a build signature, a content signature, or a timestamp
2327 # "signature". Since we no longer use build signatures, the
2328 # best we can do is look at the length and if it's thirty two,
2329 # assume that it was (or might have been) a content signature.
2330 # If it was actually a build signature, then it will cause a
2331 # rebuild anyway when it doesn't match the new content signature,
2332 # but that's probably the best we can do.
2333 import SCons.SConsign
2334 new_entry = SCons.SConsign.SConsignEntry()
2335 new_entry.binfo = self.new_binfo()
2336 binfo = new_entry.binfo
2337 for attr in self.convert_copy_attrs:
2339 value = getattr(old_entry, attr)
2340 except AttributeError:
2343 setattr(binfo, attr, value)
2344 delattr(old_entry, attr)
2345 for attr in self.convert_sig_attrs:
2347 sig_list = getattr(old_entry, attr)
2348 except AttributeError:
2352 for sig in sig_list:
2353 ninfo = self.new_ninfo()
2357 ninfo.timestamp = sig
2359 setattr(binfo, attr, value)
2360 delattr(old_entry, attr)
2363 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2365 def get_stored_info(self):
2367 return self._memo['get_stored_info']
2372 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2373 except (KeyError, OSError):
2374 import SCons.SConsign
2375 sconsign_entry = SCons.SConsign.SConsignEntry()
2376 sconsign_entry.binfo = self.new_binfo()
2377 sconsign_entry.ninfo = self.new_ninfo()
2379 if isinstance(sconsign_entry, FileBuildInfo):
2380 # This is a .sconsign file from before the Big Signature
2381 # Refactoring; convert it as best we can.
2382 sconsign_entry = self.convert_old_entry(sconsign_entry)
2384 delattr(sconsign_entry.ninfo, 'bsig')
2385 except AttributeError:
2388 self._memo['get_stored_info'] = sconsign_entry
2390 return sconsign_entry
2392 def get_stored_implicit(self):
2393 binfo = self.get_stored_info().binfo
2394 binfo.prepare_dependencies()
2395 try: return binfo.bimplicit
2396 except AttributeError: return None
2398 def rel_path(self, other):
2399 return self.dir.rel_path(other)
2401 def _get_found_includes_key(self, env, scanner, path):
2402 return (id(env), id(scanner), path)
2404 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2406 def get_found_includes(self, env, scanner, path):
2407 """Return the included implicit dependencies in this file.
2408 Cache results so we only scan the file once per path
2409 regardless of how many times this information is requested.
2411 memo_key = (id(env), id(scanner), path)
2413 memo_dict = self._memo['get_found_includes']
2416 self._memo['get_found_includes'] = memo_dict
2419 return memo_dict[memo_key]
2424 result = scanner(self, env, path)
2425 result = map(lambda N: N.disambiguate(), result)
2429 memo_dict[memo_key] = result
2433 def _createDir(self):
2434 # ensure that the directories for this node are
2438 def retrieve_from_cache(self):
2439 """Try to retrieve the node's content from a cache
2441 This method is called from multiple threads in a parallel build,
2442 so only do thread safe stuff here. Do thread unsafe stuff in
2445 Returns true iff the node was successfully retrieved.
2449 if not self.is_derived():
2451 return self.get_build_env().get_CacheDir().retrieve(self)
2455 Called just after this node is successfully built.
2457 # Push this file out to cache before the superclass Node.built()
2458 # method has a chance to clear the build signature, which it
2459 # will do if this file has a source scanner.
2461 # We have to clear the memoized values *before* we push it to
2462 # cache so that the memoization of the self.exists() return
2463 # value doesn't interfere.
2464 self.clear_memoized_values()
2466 self.get_build_env().get_CacheDir().push(self)
2467 SCons.Node.Node.built(self)
2471 self.get_build_env().get_CacheDir().push_if_forced(self)
2473 ninfo = self.get_ninfo()
2475 csig = self.get_max_drift_csig()
2479 ninfo.timestamp = self.get_timestamp()
2480 ninfo.size = self.get_size()
2482 if not self.has_builder():
2483 # This is a source file, but it might have been a target file
2484 # in another build that included more of the DAG. Copy
2485 # any build information that's stored in the .sconsign file
2486 # into our binfo object so it doesn't get lost.
2487 old = self.get_stored_info()
2488 self.get_binfo().__dict__.update(old.binfo.__dict__)
2492 def find_src_builder(self):
2495 scb = self.dir.src_builder()
2497 if diskcheck_sccs(self.dir, self.name):
2498 scb = get_DefaultSCCSBuilder()
2499 elif diskcheck_rcs(self.dir, self.name):
2500 scb = get_DefaultRCSBuilder()
2506 except AttributeError:
2509 self.builder_set(scb)
2512 def has_src_builder(self):
2513 """Return whether this Node has a source builder or not.
2515 If this Node doesn't have an explicit source code builder, this
2516 is where we figure out, on the fly, if there's a transparent
2517 source code builder for it.
2519 Note that if we found a source builder, we also set the
2520 self.builder attribute, so that all of the methods that actually
2521 *build* this file don't have to do anything different.
2525 except AttributeError:
2526 scb = self.sbuilder = self.find_src_builder()
2527 return not scb is None
2529 def alter_targets(self):
2530 """Return any corresponding targets in a variant directory.
2532 if self.is_derived():
2534 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2536 def _rmv_existing(self):
2537 self.clear_memoized_values()
2538 e = Unlink(self, [], None)
2539 if isinstance(e, SCons.Errors.BuildError):
2543 # Taskmaster interface subsystem
2546 def make_ready(self):
2547 self.has_src_builder()
2551 """Prepare for this file to be created."""
2552 SCons.Node.Node.prepare(self)
2554 if self.get_state() != SCons.Node.up_to_date:
2556 if self.is_derived() and not self.precious:
2557 self._rmv_existing()
2561 except SCons.Errors.StopError, drive:
2562 desc = "No drive `%s' for target `%s'." % (drive, self)
2563 raise SCons.Errors.StopError, desc
2570 """Remove this file."""
2571 if self.exists() or self.islink():
2572 self.fs.unlink(self.path)
2576 def do_duplicate(self, src):
2578 Unlink(self, None, None)
2579 e = Link(self, src, None)
2580 if isinstance(e, SCons.Errors.BuildError):
2581 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2582 raise SCons.Errors.StopError, desc
2584 # The Link() action may or may not have actually
2585 # created the file, depending on whether the -n
2586 # option was used or not. Delete the _exists and
2587 # _rexists attributes so they can be reevaluated.
2590 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2594 return self._memo['exists']
2597 # Duplicate from source path if we are set up to do this.
2598 if self.duplicate and not self.is_derived() and not self.linked:
2599 src = self.srcnode()
2601 # At this point, src is meant to be copied in a variant directory.
2603 if src.abspath != self.abspath:
2605 self.do_duplicate(src)
2606 # Can't return 1 here because the duplication might
2607 # not actually occur if the -n option is being used.
2609 # The source file does not exist. Make sure no old
2610 # copy remains in the variant directory.
2611 if Base.exists(self) or self.islink():
2612 self.fs.unlink(self.path)
2613 # Return None explicitly because the Base.exists() call
2614 # above will have cached its value if the file existed.
2615 self._memo['exists'] = None
2617 result = Base.exists(self)
2618 self._memo['exists'] = result
2622 # SIGNATURE SUBSYSTEM
2625 def get_max_drift_csig(self):
2627 Returns the content signature currently stored for this node
2628 if it's been unmodified longer than the max_drift value, or the
2629 max_drift value is 0. Returns None otherwise.
2631 old = self.get_stored_info()
2632 mtime = self.get_timestamp()
2635 max_drift = self.fs.max_drift
2637 if (time.time() - mtime) > max_drift:
2640 if n.timestamp and n.csig and n.timestamp == mtime:
2642 except AttributeError:
2644 elif max_drift == 0:
2646 csig = old.ninfo.csig
2647 except AttributeError:
2654 Generate a node's content signature, the digested signature
2658 cache - alternate node to use for the signature cache
2659 returns - the content signature
2661 ninfo = self.get_ninfo()
2664 except AttributeError:
2667 csig = self.get_max_drift_csig()
2671 contents = self.get_contents()
2673 # This can happen if there's actually a directory on-disk,
2674 # which can be the case if they've disabled disk checks,
2675 # or if an action with a File target actually happens to
2676 # create a same-named directory by mistake.
2679 csig = SCons.Util.MD5signature(contents)
2686 # DECISION SUBSYSTEM
2689 def builder_set(self, builder):
2690 SCons.Node.Node.builder_set(self, builder)
2691 self.changed_since_last_build = self.decide_target
2693 def changed_content(self, target, prev_ni):
2694 cur_csig = self.get_csig()
2696 return cur_csig != prev_ni.csig
2697 except AttributeError:
2700 def changed_state(self, target, prev_ni):
2701 return (self.state != SCons.Node.up_to_date)
2703 def changed_timestamp_then_content(self, target, prev_ni):
2704 if not self.changed_timestamp_match(target, prev_ni):
2706 self.get_ninfo().csig = prev_ni.csig
2707 except AttributeError:
2710 return self.changed_content(target, prev_ni)
2712 def changed_timestamp_newer(self, target, prev_ni):
2714 return self.get_timestamp() > target.get_timestamp()
2715 except AttributeError:
2718 def changed_timestamp_match(self, target, prev_ni):
2720 return self.get_timestamp() != prev_ni.timestamp
2721 except AttributeError:
2724 def decide_source(self, target, prev_ni):
2725 return target.get_build_env().decide_source(self, target, prev_ni)
2727 def decide_target(self, target, prev_ni):
2728 return target.get_build_env().decide_target(self, target, prev_ni)
2730 # Initialize this Node's decider function to decide_source() because
2731 # every file is a source file until it has a Builder attached...
2732 changed_since_last_build = decide_source
2734 def is_up_to_date(self):
2736 if T: Trace('is_up_to_date(%s):' % self)
2737 if not self.exists():
2738 if T: Trace(' not self.exists():')
2739 # The file doesn't exist locally...
2742 # ...but there is one in a Repository...
2743 if not self.changed(r):
2744 if T: Trace(' changed(%s):' % r)
2745 # ...and it's even up-to-date...
2747 # ...and they'd like a local copy.
2748 e = LocalCopy(self, r, None)
2749 if isinstance(e, SCons.Errors.BuildError):
2755 if T: Trace(' None\n')
2759 if T: Trace(' self.exists(): %s\n' % r)
2762 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2766 return self._memo['rfile']
2770 if not self.exists():
2771 norm_name = _my_normcase(self.name)
2772 for dir in self.dir.get_all_rdirs():
2773 try: node = dir.entries[norm_name]
2774 except KeyError: node = dir.file_on_disk(self.name)
2775 if node and node.exists() and \
2776 (isinstance(node, File) or isinstance(node, Entry) \
2777 or not node.is_derived()):
2780 self._memo['rfile'] = result
2784 return str(self.rfile())
2786 def get_cachedir_csig(self):
2788 Fetch a Node's content signature for purposes of computing
2789 another Node's cachesig.
2791 This is a wrapper around the normal get_csig() method that handles
2792 the somewhat obscure case of using CacheDir with the -n option.
2793 Any files that don't exist would normally be "built" by fetching
2794 them from the cache, but the normal get_csig() method will try
2795 to open up the local file, which doesn't exist because the -n
2796 option meant we didn't actually pull the file from cachedir.
2797 But since the file *does* actually exist in the cachedir, we
2798 can use its contents for the csig.
2801 return self.cachedir_csig
2802 except AttributeError:
2805 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2806 if not self.exists() and cachefile and os.path.exists(cachefile):
2807 contents = open(cachefile, 'rb').read()
2808 self.cachedir_csig = SCons.Util.MD5signature(contents)
2810 self.cachedir_csig = self.get_csig()
2811 return self.cachedir_csig
2813 def get_cachedir_bsig(self):
2815 return self.cachesig
2816 except AttributeError:
2819 # Add the path to the cache signature, because multiple
2820 # targets built by the same action will all have the same
2821 # build signature, and we have to differentiate them somehow.
2822 children = self.children()
2823 sigs = map(lambda n: n.get_cachedir_csig(), children)
2824 executor = self.get_executor()
2825 sigs.append(SCons.Util.MD5signature(executor.get_contents()))
2826 sigs.append(self.path)
2827 self.cachesig = SCons.Util.MD5collect(sigs)
2828 return self.cachesig
2832 def get_default_fs():
2841 if SCons.Memoize.use_memoizer:
2842 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2844 memoizer_counters = []
2849 def filedir_lookup(self, p, fd=None):
2851 A helper method for find_file() that looks up a directory for
2852 a file we're trying to find. This only creates the Dir Node if
2853 it exists on-disk, since if the directory doesn't exist we know
2854 we won't find any files in it... :-)
2856 It would be more compact to just use this as a nested function
2857 with a default keyword argument (see the commented-out version
2858 below), but that doesn't work unless you have nested scopes,
2859 so we define it here just so this work under Python 1.5.2.
2862 fd = self.default_filedir
2863 dir, name = os.path.split(fd)
2864 drive, d = os.path.splitdrive(dir)
2865 if d in ('/', os.sep):
2866 return p.fs.get_root(drive).dir_on_disk(name)
2868 p = self.filedir_lookup(p, dir)
2871 norm_name = _my_normcase(name)
2873 node = p.entries[norm_name]
2875 return p.dir_on_disk(name)
2876 # Once we move to Python 2.2 we can do:
2877 #if isinstance(node, (Dir, Entry)):
2878 if isinstance(node, Dir) or isinstance(node, Entry):
2882 def _find_file_key(self, filename, paths, verbose=None):
2883 return (filename, paths)
2885 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2887 def find_file(self, filename, paths, verbose=None):
2889 find_file(str, [Dir()]) -> [nodes]
2891 filename - a filename to find
2892 paths - a list of directory path *nodes* to search in. Can be
2893 represented as a list, a tuple, or a callable that is
2894 called with no arguments and returns the list or tuple.
2896 returns - the node created from the found file.
2898 Find a node corresponding to either a derived file or a file
2899 that exists already.
2901 Only the first file found is returned, and none is returned
2902 if no file is found.
2904 memo_key = self._find_file_key(filename, paths)
2906 memo_dict = self._memo['find_file']
2909 self._memo['find_file'] = memo_dict
2912 return memo_dict[memo_key]
2917 if not SCons.Util.is_String(verbose):
2918 verbose = "find_file"
2919 if not callable(verbose):
2920 verbose = ' %s: ' % verbose
2921 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2923 verbose = lambda x: x
2925 filedir, filename = os.path.split(filename)
2927 # More compact code that we can't use until we drop
2928 # support for Python 1.5.2:
2930 #def filedir_lookup(p, fd=filedir):
2932 # A helper function that looks up a directory for a file
2933 # we're trying to find. This only creates the Dir Node
2934 # if it exists on-disk, since if the directory doesn't
2935 # exist we know we won't find any files in it... :-)
2937 # dir, name = os.path.split(fd)
2939 # p = filedir_lookup(p, dir)
2942 # norm_name = _my_normcase(name)
2944 # node = p.entries[norm_name]
2946 # return p.dir_on_disk(name)
2947 # # Once we move to Python 2.2 we can do:
2948 # #if isinstance(node, (Dir, Entry)):
2949 # if isinstance(node, Dir) or isinstance(node, Entry):
2952 #paths = filter(None, map(filedir_lookup, paths))
2954 self.default_filedir = filedir
2955 paths = filter(None, map(self.filedir_lookup, paths))
2959 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2960 node, d = dir.srcdir_find_file(filename)
2962 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2966 memo_dict[memo_key] = result
2970 find_file = FileFinder().find_file