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__"
50 from SCons.Debug import logInstanceCreation
54 import SCons.Node.Alias
59 from SCons.Debug import Trace
61 # The max_drift value: by default, use a cached signature value for
62 # any file that's been untouched for more than two days.
63 default_max_drift = 2*24*60*60
66 # We stringify these file system Nodes a lot. Turning a file system Node
67 # into a string is non-trivial, because the final string representation
68 # can depend on a lot of factors: whether it's a derived target or not,
69 # whether it's linked to a repository or source directory, and whether
70 # there's duplication going on. The normal technique for optimizing
71 # calculations like this is to memoize (cache) the string value, so you
72 # only have to do the calculation once.
74 # A number of the above factors, however, can be set after we've already
75 # been asked to return a string for a Node, because a Repository() or
76 # VariantDir() call or the like may not occur until later in SConscript
77 # files. So this variable controls whether we bother trying to save
78 # string values for Nodes. The wrapper interface can set this whenever
79 # they're done mucking with Repository and VariantDir and the other stuff,
80 # to let this module know it can start returning saved string values
85 def save_strings(val):
90 # Avoid unnecessary function calls by recording a Boolean value that
91 # tells us whether or not os.path.splitdrive() actually does anything
92 # on this system, and therefore whether we need to bother calling it
93 # when looking up path names in various methods below.
98 def initialize_do_splitdrive():
100 drive, path = os.path.splitdrive('X:/foo')
101 do_splitdrive = not not drive
103 initialize_do_splitdrive()
107 needs_normpath_check = None
109 def initialize_normpath_check():
111 Initialize the normpath_check regular expression.
113 This function is used by the unit tests to re-initialize the pattern
114 when testing for behavior with different values of os.sep.
116 global needs_normpath_check
118 pattern = r'.*/|\.$|\.\.$'
120 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
121 needs_normpath_check = re.compile(pattern)
123 initialize_normpath_check()
126 # SCons.Action objects for interacting with the outside world.
128 # The Node.FS methods in this module should use these actions to
129 # create and/or remove files and directories; they should *not* use
130 # os.{link,symlink,unlink,mkdir}(), etc., directly.
132 # Using these SCons.Action objects ensures that descriptions of these
133 # external activities are properly displayed, that the displays are
134 # suppressed when the -s (silent) option is used, and (most importantly)
135 # the actions are disabled when the the -n option is used, in which case
136 # there should be *no* changes to the external file system(s)...
139 if hasattr(os, 'link'):
140 def _hardlink_func(fs, src, dst):
141 # If the source is a symlink, we can't just hard-link to it
142 # because a relative symlink may point somewhere completely
143 # different. We must disambiguate the symlink and then
144 # hard-link the final destination file.
145 while fs.islink(src):
146 link = fs.readlink(src)
147 if not os.path.isabs(link):
150 src = os.path.join(os.path.dirname(src), link)
153 _hardlink_func = None
155 if hasattr(os, 'symlink'):
156 def _softlink_func(fs, src, dst):
159 _softlink_func = None
161 def _copy_func(fs, src, dest):
162 shutil.copy2(src, dest)
164 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
167 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
168 'hard-copy', 'soft-copy', 'copy']
170 Link_Funcs = [] # contains the callables of the specified duplication style
172 def set_duplicate(duplicate):
173 # Fill in the Link_Funcs list according to the argument
174 # (discarding those not available on the platform).
176 # Set up the dictionary that maps the argument names to the
177 # underlying implementations. We do this inside this function,
178 # not in the top-level module code, so that we can remap os.link
179 # and os.symlink for testing purposes.
181 'hard' : _hardlink_func,
182 'soft' : _softlink_func,
186 if not duplicate in Valid_Duplicates:
187 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
188 "should be in Valid_Duplicates")
191 for func in string.split(duplicate,'-'):
193 Link_Funcs.append(link_dict[func])
195 def LinkFunc(target, source, env):
196 # Relative paths cause problems with symbolic links, so
197 # we use absolute paths, which may be a problem for people
198 # who want to move their soft-linked src-trees around. Those
199 # people should use the 'hard-copy' mode, softlinks cannot be
200 # used for that; at least I have no idea how ...
201 src = source[0].abspath
202 dest = target[0].abspath
203 dir, file = os.path.split(dest)
204 if dir and not target[0].fs.isdir(dir):
207 # Set a default order of link functions.
208 set_duplicate('hard-soft-copy')
210 # Now link the files with the previously specified order.
211 for func in Link_Funcs:
215 except (IOError, OSError):
216 # An OSError indicates something happened like a permissions
217 # problem or an attempt to symlink across file-system
218 # boundaries. An IOError indicates something like the file
219 # not existing. In either case, keeping trying additional
220 # functions in the list and only raise an error if the last
222 if func == Link_Funcs[-1]:
223 # exception of the last link method (copy) are fatal
229 Link = SCons.Action.Action(LinkFunc, None)
230 def LocalString(target, source, env):
231 return 'Local copy of %s from %s' % (target[0], source[0])
233 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
235 def UnlinkFunc(target, source, env):
237 t.fs.unlink(t.abspath)
240 Unlink = SCons.Action.Action(UnlinkFunc, None)
242 def MkdirFunc(target, source, env):
245 t.fs.mkdir(t.abspath)
248 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
252 def get_MkdirBuilder():
254 if MkdirBuilder is None:
256 import SCons.Defaults
257 # "env" will get filled in by Executor.get_build_env()
258 # calling SCons.Defaults.DefaultEnvironment() when necessary.
259 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
263 target_scanner = SCons.Defaults.DirEntryScanner,
264 name = "MkdirBuilder")
272 DefaultSCCSBuilder = None
273 DefaultRCSBuilder = None
275 def get_DefaultSCCSBuilder():
276 global DefaultSCCSBuilder
277 if DefaultSCCSBuilder is None:
279 # "env" will get filled in by Executor.get_build_env()
280 # calling SCons.Defaults.DefaultEnvironment() when necessary.
281 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
282 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
284 name = "DefaultSCCSBuilder")
285 return DefaultSCCSBuilder
287 def get_DefaultRCSBuilder():
288 global DefaultRCSBuilder
289 if DefaultRCSBuilder is None:
291 # "env" will get filled in by Executor.get_build_env()
292 # calling SCons.Defaults.DefaultEnvironment() when necessary.
293 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
294 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
296 name = "DefaultRCSBuilder")
297 return DefaultRCSBuilder
299 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
300 _is_cygwin = sys.platform == "cygwin"
301 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
306 return string.upper(x)
311 def __init__(self, type, do, ignore):
317 self.__call__ = self.do
318 def set_ignore(self):
319 self.__call__ = self.ignore
321 if self.type in list:
326 def do_diskcheck_match(node, predicate, errorfmt):
329 # If calling the predicate() cached a None value from stat(),
330 # remove it so it doesn't interfere with later attempts to
331 # build this Node as we walk the DAG. (This isn't a great way
332 # to do this, we're reaching into an interface that doesn't
333 # really belong to us, but it's all about performance, so
334 # for now we'll just document the dependency...)
335 if node._memo['stat'] is None:
336 del node._memo['stat']
337 except (AttributeError, KeyError):
340 raise TypeError, errorfmt % node.abspath
342 def ignore_diskcheck_match(node, predicate, errorfmt):
345 def do_diskcheck_rcs(node, name):
347 rcs_dir = node.rcs_dir
348 except AttributeError:
349 if node.entry_exists_on_disk('RCS'):
350 rcs_dir = node.Dir('RCS')
353 node.rcs_dir = rcs_dir
355 return rcs_dir.entry_exists_on_disk(name+',v')
358 def ignore_diskcheck_rcs(node, name):
361 def do_diskcheck_sccs(node, name):
363 sccs_dir = node.sccs_dir
364 except AttributeError:
365 if node.entry_exists_on_disk('SCCS'):
366 sccs_dir = node.Dir('SCCS')
369 node.sccs_dir = sccs_dir
371 return sccs_dir.entry_exists_on_disk('s.'+name)
374 def ignore_diskcheck_sccs(node, name):
377 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
378 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
379 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
387 def set_diskcheck(list):
388 for dc in diskcheckers:
391 def diskcheck_types():
392 return map(lambda dc: dc.type, diskcheckers)
396 class EntryProxy(SCons.Util.Proxy):
397 def __get_abspath(self):
399 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
400 entry.name + "_abspath")
402 def __get_filebase(self):
403 name = self.get().name
404 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
407 def __get_suffix(self):
408 name = self.get().name
409 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
412 def __get_file(self):
413 name = self.get().name
414 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
416 def __get_base_path(self):
417 """Return the file's directory and file name, with the
420 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
421 entry.name + "_base")
423 def __get_posix_path(self):
424 """Return the path with / as the path separator,
425 regardless of platform."""
430 r = string.replace(entry.get_path(), os.sep, '/')
431 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
433 def __get_windows_path(self):
434 """Return the path with \ as the path separator,
435 regardless of platform."""
440 r = string.replace(entry.get_path(), os.sep, '\\')
441 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
443 def __get_srcnode(self):
444 return EntryProxy(self.get().srcnode())
446 def __get_srcdir(self):
447 """Returns the directory containing the source node linked to this
448 node via VariantDir(), or the directory of this node if not linked."""
449 return EntryProxy(self.get().srcnode().dir)
451 def __get_rsrcnode(self):
452 return EntryProxy(self.get().srcnode().rfile())
454 def __get_rsrcdir(self):
455 """Returns the directory containing the source node linked to this
456 node via VariantDir(), or the directory of this node if not linked."""
457 return EntryProxy(self.get().srcnode().rfile().dir)
460 return EntryProxy(self.get().dir)
462 dictSpecialAttrs = { "base" : __get_base_path,
463 "posix" : __get_posix_path,
464 "windows" : __get_windows_path,
465 "win32" : __get_windows_path,
466 "srcpath" : __get_srcnode,
467 "srcdir" : __get_srcdir,
469 "abspath" : __get_abspath,
470 "filebase" : __get_filebase,
471 "suffix" : __get_suffix,
473 "rsrcpath" : __get_rsrcnode,
474 "rsrcdir" : __get_rsrcdir,
477 def __getattr__(self, name):
478 # This is how we implement the "special" attributes
479 # such as base, posix, srcdir, etc.
481 attr_function = self.dictSpecialAttrs[name]
484 attr = SCons.Util.Proxy.__getattr__(self, name)
485 except AttributeError:
487 classname = string.split(str(entry.__class__), '.')[-1]
488 if classname[-2:] == "'>":
489 # new-style classes report their name as:
490 # "<class 'something'>"
491 # instead of the classic classes:
493 classname = classname[:-2]
494 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
497 return attr_function(self)
499 class Base(SCons.Node.Node):
500 """A generic class for file system entries. This class is for
501 when we don't know yet whether the entry being looked up is a file
502 or a directory. Instances of this class can morph into either
503 Dir or File objects by a later, more precise lookup.
505 Note: this class does not define __cmp__ and __hash__ for
506 efficiency reasons. SCons does a lot of comparing of
507 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
508 as fast as possible, which means we want to use Python's built-in
509 object identity comparisons.
512 memoizer_counters = []
514 def __init__(self, name, directory, fs):
515 """Initialize a generic Node.FS.Base object.
517 Call the superclass initialization, take care of setting up
518 our relative and absolute paths, identify our parent
519 directory, and indicate that this node should use
521 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
522 SCons.Node.Node.__init__(self)
525 self.suffix = SCons.Util.splitext(name)[1]
528 assert directory, "A directory must be provided"
530 self.abspath = directory.entry_abspath(name)
531 self.labspath = directory.entry_labspath(name)
532 if directory.path == '.':
535 self.path = directory.entry_path(name)
536 if directory.tpath == '.':
539 self.tpath = directory.entry_tpath(name)
540 self.path_elements = directory.path_elements + [self]
543 self.cwd = None # will hold the SConscript directory for target nodes
544 self.duplicate = directory.duplicate
546 def must_be_same(self, klass):
548 This node, which already existed, is being looked up as the
549 specified klass. Raise an exception if it isn't.
551 if self.__class__ is klass or klass is Entry:
553 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
554 (self.__class__.__name__, self.path, klass.__name__)
559 def get_suffix(self):
566 """A Node.FS.Base object's string representation is its path
570 return self._save_str()
571 return self._get_str()
573 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
577 return self._memo['_save_str']
580 result = self._get_str()
581 self._memo['_save_str'] = result
586 if self.duplicate or self.is_derived():
587 return self.get_path()
588 srcnode = self.srcnode()
589 if srcnode.stat() is None and not self.stat() is None:
590 result = self.get_path()
592 result = srcnode.get_path()
594 # We're not at the point where we're saving the string string
595 # representations of FS Nodes (because we haven't finished
596 # reading the SConscript files and need to have str() return
597 # things relative to them). That also means we can't yet
598 # cache values returned (or not returned) by stat(), since
599 # Python code in the SConscript files might still create
600 # or otherwise affect the on-disk file. So get rid of the
601 # values that the underlying stat() method saved.
602 try: del self._memo['stat']
603 except KeyError: pass
604 if not self is srcnode:
605 try: del srcnode._memo['stat']
606 except KeyError: pass
611 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
614 try: return self._memo['stat']
615 except KeyError: pass
616 try: result = self.fs.stat(self.abspath)
617 except os.error: result = None
618 self._memo['stat'] = result
622 return not self.stat() is None
625 return self.rfile().exists()
629 if st: return st[stat.ST_MTIME]
634 if st: return st[stat.ST_SIZE]
639 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
643 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
645 if hasattr(os, 'symlink'):
647 try: st = self.fs.lstat(self.abspath)
648 except os.error: return 0
649 return stat.S_ISLNK(st[stat.ST_MODE])
652 return 0 # no symlinks
654 def is_under(self, dir):
658 return self.dir.is_under(dir)
664 """If this node is in a build path, return the node
665 corresponding to its source file. Otherwise, return
668 srcdir_list = self.dir.srcdir_list()
670 srcnode = srcdir_list[0].Entry(self.name)
671 srcnode.must_be_same(self.__class__)
675 def get_path(self, dir=None):
676 """Return path relative to the current working directory of the
677 Node.FS.Base object that owns us."""
679 dir = self.fs.getcwd()
682 path_elems = self.path_elements
683 try: i = path_elems.index(dir)
684 except ValueError: pass
685 else: path_elems = path_elems[i+1:]
686 path_elems = map(lambda n: n.name, path_elems)
687 return string.join(path_elems, os.sep)
689 def set_src_builder(self, builder):
690 """Set the source code builder for this node."""
691 self.sbuilder = builder
692 if not self.has_builder():
693 self.builder_set(builder)
695 def src_builder(self):
696 """Fetch the source code builder for this node.
698 If there isn't one, we cache the source code builder specified
699 for the directory (which in turn will cache the value from its
700 parent directory, and so on up to the file system root).
704 except AttributeError:
705 scb = self.dir.src_builder()
709 def get_abspath(self):
710 """Get the absolute path of the file."""
713 def for_signature(self):
714 # Return just our name. Even an absolute path would not work,
715 # because that can change thanks to symlinks or remapped network
719 def get_subst_proxy(self):
722 except AttributeError:
723 ret = EntryProxy(self)
727 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
730 Generates a target entry that corresponds to this entry (usually
731 a source file) with the specified prefix and suffix.
733 Note that this method can be overridden dynamically for generated
734 files that need different behavior. See Tool/swig.py for
737 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
739 def _Rfindalldirs_key(self, pathlist):
742 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
744 def Rfindalldirs(self, pathlist):
746 Return all of the directories for a given path list, including
747 corresponding "backing" directories in any repositories.
749 The Node lookups are relative to this Node (typically a
750 directory), so memoizing result saves cycles from looking
751 up the same path for each target in a given directory.
754 memo_dict = self._memo['Rfindalldirs']
757 self._memo['Rfindalldirs'] = memo_dict
760 return memo_dict[pathlist]
764 create_dir_relative_to_self = self.Dir
766 for path in pathlist:
767 if isinstance(path, SCons.Node.Node):
770 dir = create_dir_relative_to_self(path)
771 result.extend(dir.get_all_rdirs())
773 memo_dict[pathlist] = result
777 def RDirs(self, pathlist):
778 """Search for a list of directories in the Repository list."""
779 cwd = self.cwd or self.fs._cwd
780 return cwd.Rfindalldirs(pathlist)
782 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
786 return self._memo['rentry']
790 if not self.exists():
791 norm_name = _my_normcase(self.name)
792 for dir in self.dir.get_all_rdirs():
794 node = dir.entries[norm_name]
796 if dir.entry_exists_on_disk(self.name):
797 result = dir.Entry(self.name)
799 self._memo['rentry'] = result
802 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
806 """This is the class for generic Node.FS entries--that is, things
807 that could be a File or a Dir, but we're just not sure yet.
808 Consequently, the methods in this class really exist just to
809 transform their associated object into the right class when the
810 time comes, and then call the same-named method in the transformed
813 def diskcheck_match(self):
816 def disambiguate(self, must_exist=None):
823 self.__class__ = File
827 # There was nothing on-disk at this location, so look in
830 # We can't just use self.srcnode() straight away because
831 # that would create an actual Node for this file in the src
832 # directory, and there might not be one. Instead, use the
833 # dir_on_disk() method to see if there's something on-disk
834 # with that name, in which case we can go ahead and call
835 # self.srcnode() to create the right type of entry.
836 srcdir = self.dir.srcnode()
837 if srcdir != self.dir and \
838 srcdir.entry_exists_on_disk(self.name) and \
839 self.srcnode().isdir():
843 msg = "No such file or directory: '%s'" % self.abspath
844 raise SCons.Errors.UserError, msg
846 self.__class__ = File
852 """We're a generic Entry, but the caller is actually looking for
853 a File at this point, so morph into one."""
854 self.__class__ = File
857 return File.rfile(self)
859 def scanner_key(self):
860 return self.get_suffix()
862 def get_contents(self):
863 """Fetch the contents of the entry.
865 Since this should return the real contents from the file
866 system, we check to see into what sort of subclass we should
869 self = self.disambiguate(must_exist=1)
870 except SCons.Errors.UserError:
871 # There was nothing on disk with which to disambiguate
872 # this entry. Leave it as an Entry, but return a null
873 # string so calls to get_contents() in emitters and the
874 # like (e.g. in qt.py) don't have to disambiguate by hand
875 # or catch the exception.
878 return self.get_contents()
880 def must_be_same(self, klass):
881 """Called to make sure a Node is a Dir. Since we're an
882 Entry, we can morph into one."""
883 if not self.__class__ is klass:
884 self.__class__ = klass
888 # The following methods can get called before the Taskmaster has
889 # had a chance to call disambiguate() directly to see if this Entry
890 # should really be a Dir or a File. We therefore use these to call
891 # disambiguate() transparently (from our caller's point of view).
893 # Right now, this minimal set of methods has been derived by just
894 # looking at some of the methods that will obviously be called early
895 # in any of the various Taskmasters' calling sequences, and then
896 # empirically figuring out which additional methods are necessary
897 # to make various tests pass.
900 """Return if the Entry exists. Check the file system to see
901 what we should turn into first. Assume a file if there's no
903 return self.disambiguate().exists()
905 def rel_path(self, other):
906 d = self.disambiguate()
907 if d.__class__ == Entry:
908 raise "rel_path() could not disambiguate File/Dir"
909 return d.rel_path(other)
912 return self.disambiguate().new_ninfo()
914 def changed_since_last_build(self, target, prev_ni):
915 return self.disambiguate().changed_since_last_build(target, prev_ni)
917 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
918 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
920 # This is for later so we can differentiate between Entry the class and Entry
921 # the method of the FS class.
927 if SCons.Memoize.use_memoizer:
928 __metaclass__ = SCons.Memoize.Memoized_Metaclass
930 # This class implements an abstraction layer for operations involving
931 # a local file system. Essentially, this wraps any function in
932 # the os, os.path or shutil modules that we use to actually go do
933 # anything with or to the local file system.
935 # Note that there's a very good chance we'll refactor this part of
936 # the architecture in some way as we really implement the interface(s)
937 # for remote file system Nodes. For example, the right architecture
938 # might be to have this be a subclass instead of a base class.
939 # Nevertheless, we're using this as a first step in that direction.
941 # We're not using chdir() yet because the calling subclass method
942 # needs to use os.chdir() directly to avoid recursion. Will we
943 # really need this one?
944 #def chdir(self, path):
945 # return os.chdir(path)
946 def chmod(self, path, mode):
947 return os.chmod(path, mode)
948 def copy(self, src, dst):
949 return shutil.copy(src, dst)
950 def copy2(self, src, dst):
951 return shutil.copy2(src, dst)
952 def exists(self, path):
953 return os.path.exists(path)
954 def getmtime(self, path):
955 return os.path.getmtime(path)
956 def getsize(self, path):
957 return os.path.getsize(path)
958 def isdir(self, path):
959 return os.path.isdir(path)
960 def isfile(self, path):
961 return os.path.isfile(path)
962 def link(self, src, dst):
963 return os.link(src, dst)
964 def lstat(self, path):
965 return os.lstat(path)
966 def listdir(self, path):
967 return os.listdir(path)
968 def makedirs(self, path):
969 return os.makedirs(path)
970 def mkdir(self, path):
971 return os.mkdir(path)
972 def rename(self, old, new):
973 return os.rename(old, new)
974 def stat(self, path):
976 def symlink(self, src, dst):
977 return os.symlink(src, dst)
978 def open(self, path):
980 def unlink(self, path):
981 return os.unlink(path)
983 if hasattr(os, 'symlink'):
984 def islink(self, path):
985 return os.path.islink(path)
987 def islink(self, path):
988 return 0 # no symlinks
990 if hasattr(os, 'readlink'):
991 def readlink(self, file):
992 return os.readlink(file)
994 def readlink(self, file):
999 # # Skeleton for the obvious methods we might need from the
1000 # # abstraction layer for a remote filesystem.
1001 # def upload(self, local_src, remote_dst):
1003 # def download(self, remote_src, local_dst):
1009 memoizer_counters = []
1011 def __init__(self, path = None):
1012 """Initialize the Node.FS subsystem.
1014 The supplied path is the top of the source tree, where we
1015 expect to find the top-level build file. If no path is
1016 supplied, the current directory is the default.
1018 The path argument must be a valid absolute path.
1020 if __debug__: logInstanceCreation(self, 'Node.FS')
1025 self.SConstruct_dir = None
1026 self.max_drift = default_max_drift
1030 self.pathTop = os.getcwd()
1033 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1035 self.Top = self.Dir(self.pathTop)
1037 self.Top.tpath = '.'
1038 self._cwd = self.Top
1040 DirNodeInfo.fs = self
1041 FileNodeInfo.fs = self
1043 def set_SConstruct_dir(self, dir):
1044 self.SConstruct_dir = dir
1046 def get_max_drift(self):
1047 return self.max_drift
1049 def set_max_drift(self, max_drift):
1050 self.max_drift = max_drift
1055 def chdir(self, dir, change_os_dir=0):
1056 """Change the current working directory for lookups.
1057 If change_os_dir is true, we will also change the "real" cwd
1065 os.chdir(dir.abspath)
1070 def get_root(self, drive):
1072 Returns the root directory for the specified drive, creating
1075 drive = _my_normcase(drive)
1077 return self.Root[drive]
1079 root = RootDir(drive, self)
1080 self.Root[drive] = root
1082 self.Root[self.defaultDrive] = root
1083 elif drive == self.defaultDrive:
1084 self.Root[''] = root
1087 def _lookup(self, p, directory, fsclass, create=1):
1089 The generic entry point for Node lookup with user-supplied data.
1091 This translates arbitrary input into a canonical Node.FS object
1092 of the specified fsclass. The general approach for strings is
1093 to turn it into a fully normalized absolute path and then call
1094 the root directory's lookup_abs() method for the heavy lifting.
1096 If the path name begins with '#', it is unconditionally
1097 interpreted relative to the top-level directory of this FS. '#'
1098 is treated as a synonym for the top-level SConstruct directory,
1099 much like '~' is treated as a synonym for the user's home
1100 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1101 to the 'foo' subdirectory underneath the top-level SConstruct
1104 If the path name is relative, then the path is looked up relative
1105 to the specified directory, or the current directory (self._cwd,
1106 typically the SConscript directory) if the specified directory
1109 if isinstance(p, Base):
1110 # It's already a Node.FS object. Make sure it's the right
1112 p.must_be_same(fsclass)
1114 # str(p) in case it's something like a proxy object
1117 initial_hash = (p[0:1] == '#')
1119 # There was an initial '#', so we strip it and override
1120 # whatever directory they may have specified with the
1121 # top-level SConstruct directory.
1123 directory = self.Top
1125 if directory and not isinstance(directory, Dir):
1126 directory = self.Dir(directory)
1129 drive, p = os.path.splitdrive(p)
1133 # This causes a naked drive letter to be treated as a synonym
1134 # for the root directory on that drive.
1136 absolute = os.path.isabs(p)
1138 needs_normpath = needs_normpath_check.match(p)
1140 if initial_hash or not absolute:
1141 # This is a relative lookup, either to the top-level
1142 # SConstruct directory (because of the initial '#') or to
1143 # the current directory (the path name is not absolute).
1144 # Add the string to the appropriate directory lookup path,
1145 # after which the whole thing gets normalized.
1147 directory = self._cwd
1149 p = directory.labspath + '/' + p
1151 p = directory.labspath
1154 p = os.path.normpath(p)
1156 if drive or absolute:
1157 root = self.get_root(drive)
1160 directory = self._cwd
1161 root = directory.root
1164 p = string.replace(p, os.sep, '/')
1165 return root._lookup_abs(p, fsclass, create)
1167 def Entry(self, name, directory = None, create = 1):
1168 """Lookup or create a generic Entry node with the specified name.
1169 If the name is a relative path (begins with ./, ../, or a file
1170 name), then it is looked up relative to the supplied directory
1171 node, or to the top level directory of the FS (supplied at
1172 construction time) if no directory is supplied.
1174 return self._lookup(name, directory, Entry, create)
1176 def File(self, name, directory = None, create = 1):
1177 """Lookup or create a File node with the specified name. If
1178 the name is a relative path (begins with ./, ../, or a file name),
1179 then it is looked up relative to the supplied directory node,
1180 or to the top level directory of the FS (supplied at construction
1181 time) if no directory is supplied.
1183 This method will raise TypeError if a directory is found at the
1186 return self._lookup(name, directory, File, create)
1188 def Dir(self, name, directory = None, create = True):
1189 """Lookup or create a Dir node with the specified name. If
1190 the name is a relative path (begins with ./, ../, or a file name),
1191 then it is looked up relative to the supplied directory node,
1192 or to the top level directory of the FS (supplied at construction
1193 time) if no directory is supplied.
1195 This method will raise TypeError if a normal file is found at the
1198 return self._lookup(name, directory, Dir, create)
1200 def VariantDir(self, variant_dir, src_dir, duplicate=1):
1201 """Link the supplied variant directory to the source directory
1202 for purposes of building files."""
1204 if not isinstance(src_dir, SCons.Node.Node):
1205 src_dir = self.Dir(src_dir)
1206 if not isinstance(variant_dir, SCons.Node.Node):
1207 variant_dir = self.Dir(variant_dir)
1208 if src_dir.is_under(variant_dir):
1209 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1210 if variant_dir.srcdir:
1211 if variant_dir.srcdir == src_dir:
1212 return # We already did this.
1213 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1214 variant_dir.link(src_dir, duplicate)
1216 def Repository(self, *dirs):
1217 """Specify Repository directories to search."""
1219 if not isinstance(d, SCons.Node.Node):
1221 self.Top.addRepository(d)
1223 def variant_dir_target_climb(self, orig, dir, tail):
1224 """Create targets in corresponding variant directories
1226 Climb the directory tree, and look up path names
1227 relative to any linked variant directories we find.
1229 Even though this loops and walks up the tree, we don't memoize
1230 the return value because this is really only used to process
1231 the command-line targets.
1235 fmt = "building associated VariantDir targets: %s"
1238 for bd in dir.variant_dirs:
1239 if start_dir.is_under(bd):
1240 # If already in the build-dir location, don't reflect
1241 return [orig], fmt % str(orig)
1242 p = apply(os.path.join, [bd.path] + tail)
1243 targets.append(self.Entry(p))
1244 tail = [dir.name] + tail
1247 message = fmt % string.join(map(str, targets))
1248 return targets, message
1250 def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1254 This is mainly a shim layer
1258 return cwd.glob(pathname, ondisk, source, strings)
1260 class DirNodeInfo(SCons.Node.NodeInfoBase):
1261 # This should get reset by the FS initialization.
1262 current_version_id = 1
1266 def str_to_node(self, s):
1270 drive, s = os.path.splitdrive(s)
1272 root = self.fs.get_root(drive)
1273 if not os.path.isabs(s):
1274 s = top.labspath + '/' + s
1275 return root._lookup_abs(s, Entry)
1277 class DirBuildInfo(SCons.Node.BuildInfoBase):
1278 current_version_id = 1
1280 glob_magic_check = re.compile('[*?[]')
1282 def has_glob_magic(s):
1283 return glob_magic_check.search(s) is not None
1286 """A class for directories in a file system.
1289 memoizer_counters = []
1291 NodeInfo = DirNodeInfo
1292 BuildInfo = DirBuildInfo
1294 def __init__(self, name, directory, fs):
1295 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1296 Base.__init__(self, name, directory, fs)
1300 """Turn a file system Node (either a freshly initialized directory
1301 object or a separate Entry object) into a proper directory object.
1303 Set up this directory's entries and hook it into the file
1304 system tree. Specify that directories (this Node) don't use
1305 signatures for calculating whether they're current.
1308 self.repositories = []
1312 self.entries['.'] = self
1313 self.entries['..'] = self.dir
1316 self._sconsign = None
1317 self.variant_dirs = []
1318 self.root = self.dir.root
1320 # Don't just reset the executor, replace its action list,
1321 # because it might have some pre-or post-actions that need to
1323 self.builder = get_MkdirBuilder()
1324 self.get_executor().set_action_list(self.builder.action)
1326 def diskcheck_match(self):
1327 diskcheck_match(self, self.isfile,
1328 "File %s found where directory expected.")
1330 def __clearRepositoryCache(self, duplicate=None):
1331 """Called when we change the repository(ies) for a directory.
1332 This clears any cached information that is invalidated by changing
1335 for node in self.entries.values():
1336 if node != self.dir:
1337 if node != self and isinstance(node, Dir):
1338 node.__clearRepositoryCache(duplicate)
1343 except AttributeError:
1345 if duplicate != None:
1346 node.duplicate=duplicate
1348 def __resetDuplicate(self, node):
1350 node.duplicate = node.get_dir().duplicate
1352 def Entry(self, name):
1354 Looks up or creates an entry node named 'name' relative to
1357 return self.fs.Entry(name, self)
1359 def Dir(self, name, create=True):
1361 Looks up or creates a directory node named 'name' relative to
1364 dir = self.fs.Dir(name, self, create)
1367 def File(self, name):
1369 Looks up or creates a file node named 'name' relative to
1372 return self.fs.File(name, self)
1374 def _lookup_rel(self, name, klass, create=1):
1376 Looks up a *normalized* relative path name, relative to this
1379 This method is intended for use by internal lookups with
1380 already-normalized path data. For general-purpose lookups,
1381 use the Entry(), Dir() and File() methods above.
1383 This method does *no* input checking and will die or give
1384 incorrect results if it's passed a non-normalized path name (e.g.,
1385 a path containing '..'), an absolute path name, a top-relative
1386 ('#foo') path name, or any kind of object.
1388 name = self.entry_labspath(name)
1389 return self.root._lookup_abs(name, klass, create)
1391 def link(self, srcdir, duplicate):
1392 """Set this directory as the variant directory for the
1393 supplied source directory."""
1394 self.srcdir = srcdir
1395 self.duplicate = duplicate
1396 self.__clearRepositoryCache(duplicate)
1397 srcdir.variant_dirs.append(self)
1399 def getRepositories(self):
1400 """Returns a list of repositories for this directory.
1402 if self.srcdir and not self.duplicate:
1403 return self.srcdir.get_all_rdirs() + self.repositories
1404 return self.repositories
1406 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1408 def get_all_rdirs(self):
1410 return self._memo['get_all_rdirs']
1418 for rep in dir.getRepositories():
1419 result.append(rep.Dir(fname))
1423 fname = dir.name + os.sep + fname
1426 self._memo['get_all_rdirs'] = result
1430 def addRepository(self, dir):
1431 if dir != self and not dir in self.repositories:
1432 self.repositories.append(dir)
1434 self.__clearRepositoryCache()
1437 return self.entries['..']
1439 def _rel_path_key(self, other):
1442 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1444 def rel_path(self, other):
1445 """Return a path to "other" relative to this directory.
1448 # This complicated and expensive method, which constructs relative
1449 # paths between arbitrary Node.FS objects, is no longer used
1450 # by SCons itself. It was introduced to store dependency paths
1451 # in .sconsign files relative to the target, but that ended up
1452 # being significantly inefficient.
1454 # We're continuing to support the method because some SConstruct
1455 # files out there started using it when it was available, and
1456 # we're all about backwards compatibility..
1459 memo_dict = self._memo['rel_path']
1462 self._memo['rel_path'] = memo_dict
1465 return memo_dict[other]
1473 elif not other in self.path_elements:
1476 other_dir = other.get_dir()
1477 except AttributeError:
1480 if other_dir is None:
1483 dir_rel_path = self.rel_path(other_dir)
1484 if dir_rel_path == '.':
1487 result = dir_rel_path + os.sep + other.name
1491 i = self.path_elements.index(other) + 1
1493 path_elems = ['..'] * (len(self.path_elements) - i) \
1494 + map(lambda n: n.name, other.path_elements[i:])
1496 result = string.join(path_elems, os.sep)
1498 memo_dict[other] = result
1502 def get_env_scanner(self, env, kw={}):
1503 import SCons.Defaults
1504 return SCons.Defaults.DirEntryScanner
1506 def get_target_scanner(self):
1507 import SCons.Defaults
1508 return SCons.Defaults.DirEntryScanner
1510 def get_found_includes(self, env, scanner, path):
1511 """Return this directory's implicit dependencies.
1513 We don't bother caching the results because the scan typically
1514 shouldn't be requested more than once (as opposed to scanning
1515 .h file contents, which can be requested as many times as the
1516 files is #included by other files).
1520 # Clear cached info for this Dir. If we already visited this
1521 # directory on our walk down the tree (because we didn't know at
1522 # that point it was being used as the source for another Node)
1523 # then we may have calculated build signature before realizing
1524 # we had to scan the disk. Now that we have to, though, we need
1525 # to invalidate the old calculated signature so that any node
1526 # dependent on our directory structure gets one that includes
1527 # info about everything on disk.
1529 return scanner(self, env, path)
1532 # Taskmaster interface subsystem
1538 def build(self, **kw):
1539 """A null "builder" for directories."""
1541 if not self.builder is MkdirBuilder:
1542 apply(SCons.Node.Node.build, [self,], kw)
1549 """Create this directory, silently and without worrying about
1550 whether the builder is the default or not."""
1556 listDirs.append(parent)
1559 raise SCons.Errors.StopError, parent.path
1562 for dirnode in listDirs:
1564 # Don't call dirnode.build(), call the base Node method
1565 # directly because we definitely *must* create this
1566 # directory. The dirnode.build() method will suppress
1567 # the build if it's the default builder.
1568 SCons.Node.Node.build(dirnode)
1569 dirnode.get_executor().nullify()
1570 # The build() action may or may not have actually
1571 # created the directory, depending on whether the -n
1572 # option was used or not. Delete the _exists and
1573 # _rexists attributes so they can be reevaluated.
1578 def multiple_side_effect_has_builder(self):
1580 return not self.builder is MkdirBuilder and self.has_builder()
1582 def alter_targets(self):
1583 """Return any corresponding targets in a variant directory.
1585 return self.fs.variant_dir_target_climb(self, self, [])
1587 def scanner_key(self):
1588 """A directory does not get scanned."""
1591 def get_contents(self):
1592 """Return aggregate contents of all our children."""
1593 contents = map(lambda n: n.get_contents(), self.children())
1594 return string.join(contents, '')
1596 def do_duplicate(self, src):
1599 changed_since_last_build = SCons.Node.Node.state_has_changed
1601 def is_up_to_date(self):
1602 """If any child is not up-to-date, then this directory isn't,
1604 if not self.builder is MkdirBuilder and not self.exists():
1606 up_to_date = SCons.Node.up_to_date
1607 for kid in self.children():
1608 if kid.get_state() > up_to_date:
1613 if not self.exists():
1614 norm_name = _my_normcase(self.name)
1615 for dir in self.dir.get_all_rdirs():
1616 try: node = dir.entries[norm_name]
1617 except KeyError: node = dir.dir_on_disk(self.name)
1618 if node and node.exists() and \
1619 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1624 """Return the .sconsign file info for this directory,
1625 creating it first if necessary."""
1626 if not self._sconsign:
1627 import SCons.SConsign
1628 self._sconsign = SCons.SConsign.ForDirectory(self)
1629 return self._sconsign
1632 """Dir has a special need for srcnode()...if we
1633 have a srcdir attribute set, then that *is* our srcnode."""
1636 return Base.srcnode(self)
1638 def get_timestamp(self):
1639 """Return the latest timestamp from among our children"""
1641 for kid in self.children():
1642 if kid.get_timestamp() > stamp:
1643 stamp = kid.get_timestamp()
1646 def entry_abspath(self, name):
1647 return self.abspath + os.sep + name
1649 def entry_labspath(self, name):
1650 return self.labspath + '/' + name
1652 def entry_path(self, name):
1653 return self.path + os.sep + name
1655 def entry_tpath(self, name):
1656 return self.tpath + os.sep + name
1658 def entry_exists_on_disk(self, name):
1660 d = self.on_disk_entries
1661 except AttributeError:
1664 entries = os.listdir(self.abspath)
1668 for entry in map(_my_normcase, entries):
1670 self.on_disk_entries = d
1671 return d.has_key(_my_normcase(name))
1673 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1675 def srcdir_list(self):
1677 return self._memo['srcdir_list']
1687 result.append(dir.srcdir.Dir(dirname))
1688 dirname = dir.name + os.sep + dirname
1691 self._memo['srcdir_list'] = result
1695 def srcdir_duplicate(self, name):
1696 for dir in self.srcdir_list():
1697 if self.is_under(dir):
1698 # We shouldn't source from something in the build path;
1699 # variant_dir is probably under src_dir, in which case
1700 # we are reflecting.
1702 if dir.entry_exists_on_disk(name):
1703 srcnode = dir.Entry(name).disambiguate()
1705 node = self.Entry(name).disambiguate()
1706 node.do_duplicate(srcnode)
1712 def _srcdir_find_file_key(self, filename):
1715 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1717 def srcdir_find_file(self, filename):
1719 memo_dict = self._memo['srcdir_find_file']
1722 self._memo['srcdir_find_file'] = memo_dict
1725 return memo_dict[filename]
1730 if (isinstance(node, File) or isinstance(node, Entry)) and \
1731 (node.is_derived() or node.exists()):
1735 norm_name = _my_normcase(filename)
1737 for rdir in self.get_all_rdirs():
1738 try: node = rdir.entries[norm_name]
1739 except KeyError: node = rdir.file_on_disk(filename)
1740 else: node = func(node)
1742 result = (node, self)
1743 memo_dict[filename] = result
1746 for srcdir in self.srcdir_list():
1747 for rdir in srcdir.get_all_rdirs():
1748 try: node = rdir.entries[norm_name]
1749 except KeyError: node = rdir.file_on_disk(filename)
1750 else: node = func(node)
1752 result = (File(filename, self, self.fs), srcdir)
1753 memo_dict[filename] = result
1756 result = (None, None)
1757 memo_dict[filename] = result
1760 def dir_on_disk(self, name):
1761 if self.entry_exists_on_disk(name):
1762 try: return self.Dir(name)
1763 except TypeError: pass
1766 def file_on_disk(self, name):
1767 if self.entry_exists_on_disk(name) or \
1768 diskcheck_rcs(self, name) or \
1769 diskcheck_sccs(self, name):
1770 try: return self.File(name)
1771 except TypeError: pass
1772 node = self.srcdir_duplicate(name)
1773 if isinstance(node, Dir):
1777 def walk(self, func, arg):
1779 Walk this directory tree by calling the specified function
1780 for each directory in the tree.
1782 This behaves like the os.path.walk() function, but for in-memory
1783 Node.FS.Dir objects. The function takes the same arguments as
1784 the functions passed to os.path.walk():
1786 func(arg, dirname, fnames)
1788 Except that "dirname" will actually be the directory *Node*,
1789 not the string. The '.' and '..' entries are excluded from
1790 fnames. The fnames list may be modified in-place to filter the
1791 subdirectories visited or otherwise impose a specific order.
1792 The "arg" argument is always passed to func() and may be used
1793 in any way (or ignored, passing None is common).
1795 entries = self.entries
1796 names = entries.keys()
1799 func(arg, self, names)
1800 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1801 for dirname in filter(select_dirs, names):
1802 entries[dirname].walk(func, arg)
1804 def glob(self, pathname, ondisk=True, source=False, strings=False):
1806 Returns a list of Nodes (or strings) matching a specified
1809 Pathname patterns follow UNIX shell semantics: * matches
1810 any-length strings of any characters, ? matches any character,
1811 and [] can enclose lists or ranges of characters. Matches do
1812 not span directory separators.
1814 The matches take into account Repositories, returning local
1815 Nodes if a corresponding entry exists in a Repository (either
1816 an in-memory Node or something on disk).
1818 By defafult, the glob() function matches entries that exist
1819 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1820 argument to False (or some other non-true value) causes the glob()
1821 function to only match in-memory Nodes. The default behavior is
1822 to return both the on-disk and in-memory Nodes.
1824 The "source" argument, when true, specifies that corresponding
1825 source Nodes must be returned if you're globbing in a build
1826 directory (initialized with VariantDir()). The default behavior
1827 is to return Nodes local to the VariantDir().
1829 The "strings" argument, when true, returns the matches as strings,
1830 not Nodes. The strings are path names relative to this directory.
1832 The underlying algorithm is adapted from the glob.glob() function
1833 in the Python library (but heavily modified), and uses fnmatch()
1836 dirname, basename = os.path.split(pathname)
1838 return self._glob1(basename, ondisk, source, strings)
1839 if has_glob_magic(dirname):
1840 list = self.glob(dirname, ondisk, source, strings=False)
1842 list = [self.Dir(dirname, create=True)]
1845 r = dir._glob1(basename, ondisk, source, strings)
1847 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1851 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1853 Globs for and returns a list of entry names matching a single
1854 pattern in this directory.
1856 This searches any repositories and source directories for
1857 corresponding entries and returns a Node (or string) relative
1858 to the current directory if an entry is found anywhere.
1860 TODO: handle pattern with no wildcard
1862 search_dir_list = self.get_all_rdirs()
1863 for srcdir in self.srcdir_list():
1864 search_dir_list.extend(srcdir.get_all_rdirs())
1867 for dir in search_dir_list:
1868 # We use the .name attribute from the Node because the keys of
1869 # the dir.entries dictionary are normalized (that is, all upper
1870 # case) on case-insensitive systems like Windows.
1871 #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
1872 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1873 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1874 names.extend(node_names)
1877 disk_names = os.listdir(dir.abspath)
1881 names.extend(disk_names)
1883 # We're going to return corresponding Nodes in
1884 # the local directory, so we need to make sure
1885 # those Nodes exist. We only want to create
1886 # Nodes for the entries that will match the
1887 # specified pattern, though, which means we
1888 # need to filter the list here, even though
1889 # the overall list will also be filtered later,
1890 # after we exit this loop.
1891 if pattern[0] != '.':
1892 #disk_names = [ d for d in disk_names if d[0] != '.' ]
1893 disk_names = filter(lambda x: x[0] != '.', disk_names)
1894 disk_names = fnmatch.filter(disk_names, pattern)
1895 rep_nodes = map(dir.Entry, disk_names)
1896 #rep_nodes = [ n.disambiguate() for n in rep_nodes ]
1897 rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
1898 for node, name in zip(rep_nodes, disk_names):
1899 n = self.Entry(name)
1900 if n.__class__ != node.__class__:
1901 n.__class__ = node.__class__
1905 if pattern[0] != '.':
1906 #names = [ n for n in names if n[0] != '.' ]
1907 names = filter(lambda x: x[0] != '.', names)
1908 names = fnmatch.filter(names, pattern)
1913 #return [ self.entries[_my_normcase(n)] for n in names ]
1914 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1917 """A class for the root directory of a file system.
1919 This is the same as a Dir class, except that the path separator
1920 ('/' or '\\') is actually part of the name, so we don't need to
1921 add a separator when creating the path names of entries within
1924 def __init__(self, name, fs):
1925 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1926 # We're going to be our own parent directory (".." entry and .dir
1927 # attribute) so we have to set up some values so Base.__init__()
1928 # won't gag won't it calls some of our methods.
1933 self.path_elements = []
1936 Base.__init__(self, name, self, fs)
1938 # Now set our paths to what we really want them to be: the
1939 # initial drive letter (the name) plus the directory separator,
1940 # except for the "lookup abspath," which does not have the
1942 self.abspath = name + os.sep
1944 self.path = name + os.sep
1945 self.tpath = name + os.sep
1948 self._lookupDict = {}
1950 # The // and os.sep + os.sep entries are necessary because
1951 # os.path.normpath() seems to preserve double slashes at the
1952 # beginning of a path (presumably for UNC path names), but
1953 # collapses triple slashes to a single slash.
1954 self._lookupDict[''] = self
1955 self._lookupDict['/'] = self
1956 self._lookupDict['//'] = self
1957 self._lookupDict[os.sep] = self
1958 self._lookupDict[os.sep + os.sep] = self
1960 def must_be_same(self, klass):
1963 Base.must_be_same(self, klass)
1965 def _lookup_abs(self, p, klass, create=1):
1967 Fast (?) lookup of a *normalized* absolute path.
1969 This method is intended for use by internal lookups with
1970 already-normalized path data. For general-purpose lookups,
1971 use the FS.Entry(), FS.Dir() or FS.File() methods.
1973 The caller is responsible for making sure we're passed a
1974 normalized absolute path; we merely let Python's dictionary look
1975 up and return the One True Node.FS object for the path.
1977 If no Node for the specified "p" doesn't already exist, and
1978 "create" is specified, the Node may be created after recursive
1979 invocation to find or create the parent directory or directories.
1983 result = self._lookupDict[k]
1986 raise SCons.Errors.UserError
1987 # There is no Node for this path name, and we're allowed
1989 dir_name, file_name = os.path.split(p)
1990 dir_node = self._lookup_abs(dir_name, Dir)
1991 result = klass(file_name, dir_node, self.fs)
1992 self._lookupDict[k] = result
1993 dir_node.entries[_my_normcase(file_name)] = result
1994 dir_node.implicit = None
1996 # Double-check on disk (as configured) that the Node we
1997 # created matches whatever is out there in the real world.
1998 result.diskcheck_match()
2000 # There is already a Node for this path name. Allow it to
2001 # complain if we were looking for an inappropriate type.
2002 result.must_be_same(klass)
2008 def entry_abspath(self, name):
2009 return self.abspath + name
2011 def entry_labspath(self, name):
2014 def entry_path(self, name):
2015 return self.path + name
2017 def entry_tpath(self, name):
2018 return self.tpath + name
2020 def is_under(self, dir):
2032 def src_builder(self):
2035 class FileNodeInfo(SCons.Node.NodeInfoBase):
2036 current_version_id = 1
2038 field_list = ['csig', 'timestamp', 'size']
2040 # This should get reset by the FS initialization.
2043 def str_to_node(self, s):
2047 drive, s = os.path.splitdrive(s)
2049 root = self.fs.get_root(drive)
2050 if not os.path.isabs(s):
2051 s = top.labspath + '/' + s
2052 return root._lookup_abs(s, Entry)
2054 class FileBuildInfo(SCons.Node.BuildInfoBase):
2055 current_version_id = 1
2057 def convert_to_sconsign(self):
2059 Converts this FileBuildInfo object for writing to a .sconsign file
2061 This replaces each Node in our various dependency lists with its
2062 usual string representation: relative to the top-level SConstruct
2063 directory, or an absolute path if it's outside.
2071 except AttributeError:
2074 s = string.replace(s, os.sep, '/')
2076 for attr in ['bsources', 'bdepends', 'bimplicit']:
2078 val = getattr(self, attr)
2079 except AttributeError:
2082 setattr(self, attr, map(node_to_str, val))
2083 def convert_from_sconsign(self, dir, name):
2085 Converts a newly-read FileBuildInfo object for in-SCons use
2087 For normal up-to-date checking, we don't have any conversion to
2088 perform--but we're leaving this method here to make that clear.
2091 def prepare_dependencies(self):
2093 Prepares a FileBuildInfo object for explaining what changed
2095 The bsources, bdepends and bimplicit lists have all been
2096 stored on disk as paths relative to the top-level SConstruct
2097 directory. Convert the strings to actual Nodes (for use by the
2098 --debug=explain code and --implicit-cache).
2101 ('bsources', 'bsourcesigs'),
2102 ('bdepends', 'bdependsigs'),
2103 ('bimplicit', 'bimplicitsigs'),
2105 for (nattr, sattr) in attrs:
2107 strings = getattr(self, nattr)
2108 nodeinfos = getattr(self, sattr)
2109 except AttributeError:
2113 for s, ni in zip(strings, nodeinfos):
2114 if not isinstance(s, SCons.Node.Node):
2115 s = ni.str_to_node(s)
2117 setattr(self, nattr, nodes)
2118 def format(self, names=0):
2120 bkids = self.bsources + self.bdepends + self.bimplicit
2121 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
2122 for bkid, bkidsig in zip(bkids, bkidsigs):
2123 result.append(str(bkid) + ': ' +
2124 string.join(bkidsig.format(names=names), ' '))
2125 result.append('%s [%s]' % (self.bactsig, self.bact))
2126 return string.join(result, '\n')
2129 """A class for files in a file system.
2132 memoizer_counters = []
2134 NodeInfo = FileNodeInfo
2135 BuildInfo = FileBuildInfo
2137 def diskcheck_match(self):
2138 diskcheck_match(self, self.isdir,
2139 "Directory %s found where file expected.")
2141 def __init__(self, name, directory, fs):
2142 if __debug__: logInstanceCreation(self, 'Node.FS.File')
2143 Base.__init__(self, name, directory, fs)
2146 def Entry(self, name):
2147 """Create an entry node named 'name' relative to
2148 the SConscript directory of this file."""
2149 return self.cwd.Entry(name)
2151 def Dir(self, name, create=True):
2152 """Create a directory node named 'name' relative to
2153 the SConscript directory of this file."""
2154 return self.cwd.Dir(name, create)
2156 def Dirs(self, pathlist):
2157 """Create a list of directories relative to the SConscript
2158 directory of this file."""
2159 return map(lambda p, s=self: s.Dir(p), pathlist)
2161 def File(self, name):
2162 """Create a file node named 'name' relative to
2163 the SConscript directory of this file."""
2164 return self.cwd.File(name)
2166 #def generate_build_dict(self):
2167 # """Return an appropriate dictionary of values for building
2169 # return {'Dir' : self.Dir,
2170 # 'File' : self.File,
2171 # 'RDirs' : self.RDirs}
2174 """Turn a file system node into a File object."""
2175 self.scanner_paths = {}
2176 if not hasattr(self, '_local'):
2179 # If there was already a Builder set on this entry, then
2180 # we need to make sure we call the target-decider function,
2181 # not the source-decider. Reaching in and doing this by hand
2182 # is a little bogus. We'd prefer to handle this by adding
2183 # an Entry.builder_set() method that disambiguates like the
2184 # other methods, but that starts running into problems with the
2185 # fragile way we initialize Dir Nodes with their Mkdir builders,
2186 # yet still allow them to be overridden by the user. Since it's
2187 # not clear right now how to fix that, stick with what works
2188 # until it becomes clear...
2189 if self.has_builder():
2190 self.changed_since_last_build = self.decide_target
2192 def scanner_key(self):
2193 return self.get_suffix()
2195 def get_contents(self):
2196 if not self.rexists():
2198 fname = self.rfile().abspath
2200 r = open(fname, "rb").read()
2201 except EnvironmentError, e:
2207 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2211 return self._memo['get_size']
2216 size = self.rfile().getsize()
2220 self._memo['get_size'] = size
2224 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2226 def get_timestamp(self):
2228 return self._memo['get_timestamp']
2233 timestamp = self.rfile().getmtime()
2237 self._memo['get_timestamp'] = timestamp
2241 def store_info(self):
2242 # Merge our build information into the already-stored entry.
2243 # This accomodates "chained builds" where a file that's a target
2244 # in one build (SConstruct file) is a source in a different build.
2245 # See test/chained-build.py for the use case.
2246 self.dir.sconsign().store_info(self.name, self)
2248 convert_copy_attrs = [
2258 convert_sig_attrs = [
2264 def convert_old_entry(self, old_entry):
2265 # Convert a .sconsign entry from before the Big Signature
2266 # Refactoring, doing what we can to convert its information
2267 # to the new .sconsign entry format.
2269 # The old format looked essentially like this:
2278 # .bsourcesigs ("signature" list)
2280 # .bdependsigs ("signature" list)
2282 # .bimplicitsigs ("signature" list)
2286 # The new format looks like this:
2293 # .binfo (BuildInfo)
2295 # .bsourcesigs (NodeInfo list)
2301 # .bdependsigs (NodeInfo list)
2307 # .bimplicitsigs (NodeInfo list)
2315 # The basic idea of the new structure is that a NodeInfo always
2316 # holds all available information about the state of a given Node
2317 # at a certain point in time. The various .b*sigs lists can just
2318 # be a list of pointers to the .ninfo attributes of the different
2319 # dependent nodes, without any copying of information until it's
2320 # time to pickle it for writing out to a .sconsign file.
2322 # The complicating issue is that the *old* format only stored one
2323 # "signature" per dependency, based on however the *last* build
2324 # was configured. We don't know from just looking at it whether
2325 # it was a build signature, a content signature, or a timestamp
2326 # "signature". Since we no longer use build signatures, the
2327 # best we can do is look at the length and if it's thirty two,
2328 # assume that it was (or might have been) a content signature.
2329 # If it was actually a build signature, then it will cause a
2330 # rebuild anyway when it doesn't match the new content signature,
2331 # but that's probably the best we can do.
2332 import SCons.SConsign
2333 new_entry = SCons.SConsign.SConsignEntry()
2334 new_entry.binfo = self.new_binfo()
2335 binfo = new_entry.binfo
2336 for attr in self.convert_copy_attrs:
2338 value = getattr(old_entry, attr)
2339 except AttributeError:
2342 setattr(binfo, attr, value)
2343 delattr(old_entry, attr)
2344 for attr in self.convert_sig_attrs:
2346 sig_list = getattr(old_entry, attr)
2347 except AttributeError:
2351 for sig in sig_list:
2352 ninfo = self.new_ninfo()
2356 ninfo.timestamp = sig
2358 setattr(binfo, attr, value)
2359 delattr(old_entry, attr)
2362 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2364 def get_stored_info(self):
2366 return self._memo['get_stored_info']
2371 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2372 except (KeyError, OSError):
2373 import SCons.SConsign
2374 sconsign_entry = SCons.SConsign.SConsignEntry()
2375 sconsign_entry.binfo = self.new_binfo()
2376 sconsign_entry.ninfo = self.new_ninfo()
2378 if isinstance(sconsign_entry, FileBuildInfo):
2379 # This is a .sconsign file from before the Big Signature
2380 # Refactoring; convert it as best we can.
2381 sconsign_entry = self.convert_old_entry(sconsign_entry)
2383 delattr(sconsign_entry.ninfo, 'bsig')
2384 except AttributeError:
2387 self._memo['get_stored_info'] = sconsign_entry
2389 return sconsign_entry
2391 def get_stored_implicit(self):
2392 binfo = self.get_stored_info().binfo
2393 binfo.prepare_dependencies()
2394 try: return binfo.bimplicit
2395 except AttributeError: return None
2397 def rel_path(self, other):
2398 return self.dir.rel_path(other)
2400 def _get_found_includes_key(self, env, scanner, path):
2401 return (id(env), id(scanner), path)
2403 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2405 def get_found_includes(self, env, scanner, path):
2406 """Return the included implicit dependencies in this file.
2407 Cache results so we only scan the file once per path
2408 regardless of how many times this information is requested.
2410 memo_key = (id(env), id(scanner), path)
2412 memo_dict = self._memo['get_found_includes']
2415 self._memo['get_found_includes'] = memo_dict
2418 return memo_dict[memo_key]
2423 result = scanner(self, env, path)
2424 result = map(lambda N: N.disambiguate(), result)
2428 memo_dict[memo_key] = result
2432 def _createDir(self):
2433 # ensure that the directories for this node are
2437 def retrieve_from_cache(self):
2438 """Try to retrieve the node's content from a cache
2440 This method is called from multiple threads in a parallel build,
2441 so only do thread safe stuff here. Do thread unsafe stuff in
2444 Returns true iff the node was successfully retrieved.
2448 if not self.is_derived():
2450 return self.get_build_env().get_CacheDir().retrieve(self)
2454 Called just after this node is successfully built.
2456 # Push this file out to cache before the superclass Node.built()
2457 # method has a chance to clear the build signature, which it
2458 # will do if this file has a source scanner.
2460 # We have to clear the memoized values *before* we push it to
2461 # cache so that the memoization of the self.exists() return
2462 # value doesn't interfere.
2463 self.clear_memoized_values()
2465 self.get_build_env().get_CacheDir().push(self)
2466 SCons.Node.Node.built(self)
2470 self.get_build_env().get_CacheDir().push_if_forced(self)
2472 ninfo = self.get_ninfo()
2474 csig = self.get_max_drift_csig()
2478 ninfo.timestamp = self.get_timestamp()
2479 ninfo.size = self.get_size()
2481 if not self.has_builder():
2482 # This is a source file, but it might have been a target file
2483 # in another build that included more of the DAG. Copy
2484 # any build information that's stored in the .sconsign file
2485 # into our binfo object so it doesn't get lost.
2486 old = self.get_stored_info()
2487 self.get_binfo().__dict__.update(old.binfo.__dict__)
2491 def find_src_builder(self):
2494 scb = self.dir.src_builder()
2496 if diskcheck_sccs(self.dir, self.name):
2497 scb = get_DefaultSCCSBuilder()
2498 elif diskcheck_rcs(self.dir, self.name):
2499 scb = get_DefaultRCSBuilder()
2505 except AttributeError:
2508 self.builder_set(scb)
2511 def has_src_builder(self):
2512 """Return whether this Node has a source builder or not.
2514 If this Node doesn't have an explicit source code builder, this
2515 is where we figure out, on the fly, if there's a transparent
2516 source code builder for it.
2518 Note that if we found a source builder, we also set the
2519 self.builder attribute, so that all of the methods that actually
2520 *build* this file don't have to do anything different.
2524 except AttributeError:
2525 scb = self.sbuilder = self.find_src_builder()
2526 return not scb is None
2528 def alter_targets(self):
2529 """Return any corresponding targets in a variant directory.
2531 if self.is_derived():
2533 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2535 def _rmv_existing(self):
2536 self.clear_memoized_values()
2537 e = Unlink(self, [], None)
2538 if isinstance(e, SCons.Errors.BuildError):
2542 # Taskmaster interface subsystem
2545 def make_ready(self):
2546 self.has_src_builder()
2550 """Prepare for this file to be created."""
2551 SCons.Node.Node.prepare(self)
2553 if self.get_state() != SCons.Node.up_to_date:
2555 if self.is_derived() and not self.precious:
2556 self._rmv_existing()
2560 except SCons.Errors.StopError, drive:
2561 desc = "No drive `%s' for target `%s'." % (drive, self)
2562 raise SCons.Errors.StopError, desc
2569 """Remove this file."""
2570 if self.exists() or self.islink():
2571 self.fs.unlink(self.path)
2575 def do_duplicate(self, src):
2577 Unlink(self, None, None)
2578 e = Link(self, src, None)
2579 if isinstance(e, SCons.Errors.BuildError):
2580 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2581 raise SCons.Errors.StopError, desc
2583 # The Link() action may or may not have actually
2584 # created the file, depending on whether the -n
2585 # option was used or not. Delete the _exists and
2586 # _rexists attributes so they can be reevaluated.
2589 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2593 return self._memo['exists']
2596 # Duplicate from source path if we are set up to do this.
2597 if self.duplicate and not self.is_derived() and not self.linked:
2598 src = self.srcnode()
2600 # At this point, src is meant to be copied in a variant directory.
2602 if src.abspath != self.abspath:
2604 self.do_duplicate(src)
2605 # Can't return 1 here because the duplication might
2606 # not actually occur if the -n option is being used.
2608 # The source file does not exist. Make sure no old
2609 # copy remains in the variant directory.
2610 if Base.exists(self) or self.islink():
2611 self.fs.unlink(self.path)
2612 # Return None explicitly because the Base.exists() call
2613 # above will have cached its value if the file existed.
2614 self._memo['exists'] = None
2616 result = Base.exists(self)
2617 self._memo['exists'] = result
2621 # SIGNATURE SUBSYSTEM
2624 def get_max_drift_csig(self):
2626 Returns the content signature currently stored for this node
2627 if it's been unmodified longer than the max_drift value, or the
2628 max_drift value is 0. Returns None otherwise.
2630 old = self.get_stored_info()
2631 mtime = self.get_timestamp()
2634 max_drift = self.fs.max_drift
2636 if (time.time() - mtime) > max_drift:
2639 if n.timestamp and n.csig and n.timestamp == mtime:
2641 except AttributeError:
2643 elif max_drift == 0:
2645 csig = old.ninfo.csig
2646 except AttributeError:
2653 Generate a node's content signature, the digested signature
2657 cache - alternate node to use for the signature cache
2658 returns - the content signature
2660 ninfo = self.get_ninfo()
2663 except AttributeError:
2666 csig = self.get_max_drift_csig()
2670 contents = self.get_contents()
2672 # This can happen if there's actually a directory on-disk,
2673 # which can be the case if they've disabled disk checks,
2674 # or if an action with a File target actually happens to
2675 # create a same-named directory by mistake.
2678 csig = SCons.Util.MD5signature(contents)
2685 # DECISION SUBSYSTEM
2688 def builder_set(self, builder):
2689 SCons.Node.Node.builder_set(self, builder)
2690 self.changed_since_last_build = self.decide_target
2692 def changed_content(self, target, prev_ni):
2693 cur_csig = self.get_csig()
2695 return cur_csig != prev_ni.csig
2696 except AttributeError:
2699 def changed_state(self, target, prev_ni):
2700 return (self.state != SCons.Node.up_to_date)
2702 def changed_timestamp_then_content(self, target, prev_ni):
2703 if not self.changed_timestamp_match(target, prev_ni):
2705 self.get_ninfo().csig = prev_ni.csig
2706 except AttributeError:
2709 return self.changed_content(target, prev_ni)
2711 def changed_timestamp_newer(self, target, prev_ni):
2713 return self.get_timestamp() > target.get_timestamp()
2714 except AttributeError:
2717 def changed_timestamp_match(self, target, prev_ni):
2719 return self.get_timestamp() != prev_ni.timestamp
2720 except AttributeError:
2723 def decide_source(self, target, prev_ni):
2724 return target.get_build_env().decide_source(self, target, prev_ni)
2726 def decide_target(self, target, prev_ni):
2727 return target.get_build_env().decide_target(self, target, prev_ni)
2729 # Initialize this Node's decider function to decide_source() because
2730 # every file is a source file until it has a Builder attached...
2731 changed_since_last_build = decide_source
2733 def is_up_to_date(self):
2735 if T: Trace('is_up_to_date(%s):' % self)
2736 if not self.exists():
2737 if T: Trace(' not self.exists():')
2738 # The file doesn't exist locally...
2741 # ...but there is one in a Repository...
2742 if not self.changed(r):
2743 if T: Trace(' changed(%s):' % r)
2744 # ...and it's even up-to-date...
2746 # ...and they'd like a local copy.
2747 e = LocalCopy(self, r, None)
2748 if isinstance(e, SCons.Errors.BuildError):
2754 if T: Trace(' None\n')
2758 if T: Trace(' self.exists(): %s\n' % r)
2761 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2765 return self._memo['rfile']
2769 if not self.exists():
2770 norm_name = _my_normcase(self.name)
2771 for dir in self.dir.get_all_rdirs():
2772 try: node = dir.entries[norm_name]
2773 except KeyError: node = dir.file_on_disk(self.name)
2774 if node and node.exists() and \
2775 (isinstance(node, File) or isinstance(node, Entry) \
2776 or not node.is_derived()):
2779 self._memo['rfile'] = result
2783 return str(self.rfile())
2785 def get_cachedir_csig(self):
2787 Fetch a Node's content signature for purposes of computing
2788 another Node's cachesig.
2790 This is a wrapper around the normal get_csig() method that handles
2791 the somewhat obscure case of using CacheDir with the -n option.
2792 Any files that don't exist would normally be "built" by fetching
2793 them from the cache, but the normal get_csig() method will try
2794 to open up the local file, which doesn't exist because the -n
2795 option meant we didn't actually pull the file from cachedir.
2796 But since the file *does* actually exist in the cachedir, we
2797 can use its contents for the csig.
2800 return self.cachedir_csig
2801 except AttributeError:
2804 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2805 if not self.exists() and cachefile and os.path.exists(cachefile):
2806 contents = open(cachefile, 'rb').read()
2807 self.cachedir_csig = SCons.Util.MD5signature(contents)
2809 self.cachedir_csig = self.get_csig()
2810 return self.cachedir_csig
2812 def get_cachedir_bsig(self):
2814 return self.cachesig
2815 except AttributeError:
2818 # Add the path to the cache signature, because multiple
2819 # targets built by the same action will all have the same
2820 # build signature, and we have to differentiate them somehow.
2821 children = self.children()
2822 sigs = map(lambda n: n.get_cachedir_csig(), children)
2823 executor = self.get_executor()
2824 sigs.append(SCons.Util.MD5signature(executor.get_contents()))
2825 sigs.append(self.path)
2826 self.cachesig = SCons.Util.MD5collect(sigs)
2827 return self.cachesig
2831 def get_default_fs():
2840 if SCons.Memoize.use_memoizer:
2841 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2843 memoizer_counters = []
2848 def filedir_lookup(self, p, fd=None):
2850 A helper method for find_file() that looks up a directory for
2851 a file we're trying to find. This only creates the Dir Node if
2852 it exists on-disk, since if the directory doesn't exist we know
2853 we won't find any files in it... :-)
2855 It would be more compact to just use this as a nested function
2856 with a default keyword argument (see the commented-out version
2857 below), but that doesn't work unless you have nested scopes,
2858 so we define it here just so this work under Python 1.5.2.
2861 fd = self.default_filedir
2862 dir, name = os.path.split(fd)
2863 drive, d = os.path.splitdrive(dir)
2864 if d in ('/', os.sep):
2865 return p.fs.get_root(drive).dir_on_disk(name)
2867 p = self.filedir_lookup(p, dir)
2870 norm_name = _my_normcase(name)
2872 node = p.entries[norm_name]
2874 return p.dir_on_disk(name)
2875 # Once we move to Python 2.2 we can do:
2876 #if isinstance(node, (Dir, Entry)):
2877 if isinstance(node, Dir) or isinstance(node, Entry):
2881 def _find_file_key(self, filename, paths, verbose=None):
2882 return (filename, paths)
2884 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2886 def find_file(self, filename, paths, verbose=None):
2888 find_file(str, [Dir()]) -> [nodes]
2890 filename - a filename to find
2891 paths - a list of directory path *nodes* to search in. Can be
2892 represented as a list, a tuple, or a callable that is
2893 called with no arguments and returns the list or tuple.
2895 returns - the node created from the found file.
2897 Find a node corresponding to either a derived file or a file
2898 that exists already.
2900 Only the first file found is returned, and none is returned
2901 if no file is found.
2903 memo_key = self._find_file_key(filename, paths)
2905 memo_dict = self._memo['find_file']
2908 self._memo['find_file'] = memo_dict
2911 return memo_dict[memo_key]
2916 if not SCons.Util.is_String(verbose):
2917 verbose = "find_file"
2918 if not callable(verbose):
2919 verbose = ' %s: ' % verbose
2920 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2922 verbose = lambda x: x
2924 filedir, filename = os.path.split(filename)
2926 # More compact code that we can't use until we drop
2927 # support for Python 1.5.2:
2929 #def filedir_lookup(p, fd=filedir):
2931 # A helper function that looks up a directory for a file
2932 # we're trying to find. This only creates the Dir Node
2933 # if it exists on-disk, since if the directory doesn't
2934 # exist we know we won't find any files in it... :-)
2936 # dir, name = os.path.split(fd)
2938 # p = filedir_lookup(p, dir)
2941 # norm_name = _my_normcase(name)
2943 # node = p.entries[norm_name]
2945 # return p.dir_on_disk(name)
2946 # # Once we move to Python 2.2 we can do:
2947 # #if isinstance(node, (Dir, Entry)):
2948 # if isinstance(node, Dir) or isinstance(node, Entry):
2951 #paths = filter(None, map(filedir_lookup, paths))
2953 self.default_filedir = filedir
2954 paths = filter(None, map(self.filedir_lookup, paths))
2958 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2959 node, d = dir.srcdir_find_file(filename)
2961 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2965 memo_dict[memo_key] = result
2969 find_file = FileFinder().find_file