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 str_for_display(self):
548 return '"' + self.__str__() + '"'
550 def must_be_same(self, klass):
552 This node, which already existed, is being looked up as the
553 specified klass. Raise an exception if it isn't.
555 if self.__class__ is klass or klass is Entry:
557 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
558 (self.__class__.__name__, self.path, klass.__name__)
563 def get_suffix(self):
570 """A Node.FS.Base object's string representation is its path
574 return self._save_str()
575 return self._get_str()
577 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
581 return self._memo['_save_str']
584 result = self._get_str()
585 self._memo['_save_str'] = result
590 if self.duplicate or self.is_derived():
591 return self.get_path()
592 srcnode = self.srcnode()
593 if srcnode.stat() is None and not self.stat() is None:
594 result = self.get_path()
596 result = srcnode.get_path()
598 # We're not at the point where we're saving the string string
599 # representations of FS Nodes (because we haven't finished
600 # reading the SConscript files and need to have str() return
601 # things relative to them). That also means we can't yet
602 # cache values returned (or not returned) by stat(), since
603 # Python code in the SConscript files might still create
604 # or otherwise affect the on-disk file. So get rid of the
605 # values that the underlying stat() method saved.
606 try: del self._memo['stat']
607 except KeyError: pass
608 if not self is srcnode:
609 try: del srcnode._memo['stat']
610 except KeyError: pass
615 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
618 try: return self._memo['stat']
619 except KeyError: pass
620 try: result = self.fs.stat(self.abspath)
621 except os.error: result = None
622 self._memo['stat'] = result
626 return not self.stat() is None
629 return self.rfile().exists()
633 if st: return st[stat.ST_MTIME]
638 if st: return st[stat.ST_SIZE]
643 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
647 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
649 if hasattr(os, 'symlink'):
651 try: st = self.fs.lstat(self.abspath)
652 except os.error: return 0
653 return stat.S_ISLNK(st[stat.ST_MODE])
656 return 0 # no symlinks
658 def is_under(self, dir):
662 return self.dir.is_under(dir)
668 """If this node is in a build path, return the node
669 corresponding to its source file. Otherwise, return
672 srcdir_list = self.dir.srcdir_list()
674 srcnode = srcdir_list[0].Entry(self.name)
675 srcnode.must_be_same(self.__class__)
679 def get_path(self, dir=None):
680 """Return path relative to the current working directory of the
681 Node.FS.Base object that owns us."""
683 dir = self.fs.getcwd()
686 path_elems = self.path_elements
687 try: i = path_elems.index(dir)
688 except ValueError: pass
689 else: path_elems = path_elems[i+1:]
690 path_elems = map(lambda n: n.name, path_elems)
691 return string.join(path_elems, os.sep)
693 def set_src_builder(self, builder):
694 """Set the source code builder for this node."""
695 self.sbuilder = builder
696 if not self.has_builder():
697 self.builder_set(builder)
699 def src_builder(self):
700 """Fetch the source code builder for this node.
702 If there isn't one, we cache the source code builder specified
703 for the directory (which in turn will cache the value from its
704 parent directory, and so on up to the file system root).
708 except AttributeError:
709 scb = self.dir.src_builder()
713 def get_abspath(self):
714 """Get the absolute path of the file."""
717 def for_signature(self):
718 # Return just our name. Even an absolute path would not work,
719 # because that can change thanks to symlinks or remapped network
723 def get_subst_proxy(self):
726 except AttributeError:
727 ret = EntryProxy(self)
731 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
734 Generates a target entry that corresponds to this entry (usually
735 a source file) with the specified prefix and suffix.
737 Note that this method can be overridden dynamically for generated
738 files that need different behavior. See Tool/swig.py for
741 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
743 def _Rfindalldirs_key(self, pathlist):
746 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
748 def Rfindalldirs(self, pathlist):
750 Return all of the directories for a given path list, including
751 corresponding "backing" directories in any repositories.
753 The Node lookups are relative to this Node (typically a
754 directory), so memoizing result saves cycles from looking
755 up the same path for each target in a given directory.
758 memo_dict = self._memo['Rfindalldirs']
761 self._memo['Rfindalldirs'] = memo_dict
764 return memo_dict[pathlist]
768 create_dir_relative_to_self = self.Dir
770 for path in pathlist:
771 if isinstance(path, SCons.Node.Node):
774 dir = create_dir_relative_to_self(path)
775 result.extend(dir.get_all_rdirs())
777 memo_dict[pathlist] = result
781 def RDirs(self, pathlist):
782 """Search for a list of directories in the Repository list."""
783 cwd = self.cwd or self.fs._cwd
784 return cwd.Rfindalldirs(pathlist)
786 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
790 return self._memo['rentry']
794 if not self.exists():
795 norm_name = _my_normcase(self.name)
796 for dir in self.dir.get_all_rdirs():
798 node = dir.entries[norm_name]
800 if dir.entry_exists_on_disk(self.name):
801 result = dir.Entry(self.name)
803 self._memo['rentry'] = result
806 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
810 """This is the class for generic Node.FS entries--that is, things
811 that could be a File or a Dir, but we're just not sure yet.
812 Consequently, the methods in this class really exist just to
813 transform their associated object into the right class when the
814 time comes, and then call the same-named method in the transformed
817 def diskcheck_match(self):
820 def disambiguate(self, must_exist=None):
827 self.__class__ = File
831 # There was nothing on-disk at this location, so look in
834 # We can't just use self.srcnode() straight away because
835 # that would create an actual Node for this file in the src
836 # directory, and there might not be one. Instead, use the
837 # dir_on_disk() method to see if there's something on-disk
838 # with that name, in which case we can go ahead and call
839 # self.srcnode() to create the right type of entry.
840 srcdir = self.dir.srcnode()
841 if srcdir != self.dir and \
842 srcdir.entry_exists_on_disk(self.name) and \
843 self.srcnode().isdir():
847 msg = "No such file or directory: '%s'" % self.abspath
848 raise SCons.Errors.UserError, msg
850 self.__class__ = File
856 """We're a generic Entry, but the caller is actually looking for
857 a File at this point, so morph into one."""
858 self.__class__ = File
861 return File.rfile(self)
863 def scanner_key(self):
864 return self.get_suffix()
866 def get_contents(self):
867 """Fetch the contents of the entry.
869 Since this should return the real contents from the file
870 system, we check to see into what sort of subclass we should
873 self = self.disambiguate(must_exist=1)
874 except SCons.Errors.UserError:
875 # There was nothing on disk with which to disambiguate
876 # this entry. Leave it as an Entry, but return a null
877 # string so calls to get_contents() in emitters and the
878 # like (e.g. in qt.py) don't have to disambiguate by hand
879 # or catch the exception.
882 return self.get_contents()
884 def must_be_same(self, klass):
885 """Called to make sure a Node is a Dir. Since we're an
886 Entry, we can morph into one."""
887 if not self.__class__ is klass:
888 self.__class__ = klass
892 # The following methods can get called before the Taskmaster has
893 # had a chance to call disambiguate() directly to see if this Entry
894 # should really be a Dir or a File. We therefore use these to call
895 # disambiguate() transparently (from our caller's point of view).
897 # Right now, this minimal set of methods has been derived by just
898 # looking at some of the methods that will obviously be called early
899 # in any of the various Taskmasters' calling sequences, and then
900 # empirically figuring out which additional methods are necessary
901 # to make various tests pass.
904 """Return if the Entry exists. Check the file system to see
905 what we should turn into first. Assume a file if there's no
907 return self.disambiguate().exists()
909 def rel_path(self, other):
910 d = self.disambiguate()
911 if d.__class__ == Entry:
912 raise "rel_path() could not disambiguate File/Dir"
913 return d.rel_path(other)
916 return self.disambiguate().new_ninfo()
918 def changed_since_last_build(self, target, prev_ni):
919 return self.disambiguate().changed_since_last_build(target, prev_ni)
921 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
922 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
924 # This is for later so we can differentiate between Entry the class and Entry
925 # the method of the FS class.
931 if SCons.Memoize.use_memoizer:
932 __metaclass__ = SCons.Memoize.Memoized_Metaclass
934 # This class implements an abstraction layer for operations involving
935 # a local file system. Essentially, this wraps any function in
936 # the os, os.path or shutil modules that we use to actually go do
937 # anything with or to the local file system.
939 # Note that there's a very good chance we'll refactor this part of
940 # the architecture in some way as we really implement the interface(s)
941 # for remote file system Nodes. For example, the right architecture
942 # might be to have this be a subclass instead of a base class.
943 # Nevertheless, we're using this as a first step in that direction.
945 # We're not using chdir() yet because the calling subclass method
946 # needs to use os.chdir() directly to avoid recursion. Will we
947 # really need this one?
948 #def chdir(self, path):
949 # return os.chdir(path)
950 def chmod(self, path, mode):
951 return os.chmod(path, mode)
952 def copy(self, src, dst):
953 return shutil.copy(src, dst)
954 def copy2(self, src, dst):
955 return shutil.copy2(src, dst)
956 def exists(self, path):
957 return os.path.exists(path)
958 def getmtime(self, path):
959 return os.path.getmtime(path)
960 def getsize(self, path):
961 return os.path.getsize(path)
962 def isdir(self, path):
963 return os.path.isdir(path)
964 def isfile(self, path):
965 return os.path.isfile(path)
966 def link(self, src, dst):
967 return os.link(src, dst)
968 def lstat(self, path):
969 return os.lstat(path)
970 def listdir(self, path):
971 return os.listdir(path)
972 def makedirs(self, path):
973 return os.makedirs(path)
974 def mkdir(self, path):
975 return os.mkdir(path)
976 def rename(self, old, new):
977 return os.rename(old, new)
978 def stat(self, path):
980 def symlink(self, src, dst):
981 return os.symlink(src, dst)
982 def open(self, path):
984 def unlink(self, path):
985 return os.unlink(path)
987 if hasattr(os, 'symlink'):
988 def islink(self, path):
989 return os.path.islink(path)
991 def islink(self, path):
992 return 0 # no symlinks
994 if hasattr(os, 'readlink'):
995 def readlink(self, file):
996 return os.readlink(file)
998 def readlink(self, file):
1003 # # Skeleton for the obvious methods we might need from the
1004 # # abstraction layer for a remote filesystem.
1005 # def upload(self, local_src, remote_dst):
1007 # def download(self, remote_src, local_dst):
1013 memoizer_counters = []
1015 def __init__(self, path = None):
1016 """Initialize the Node.FS subsystem.
1018 The supplied path is the top of the source tree, where we
1019 expect to find the top-level build file. If no path is
1020 supplied, the current directory is the default.
1022 The path argument must be a valid absolute path.
1024 if __debug__: logInstanceCreation(self, 'Node.FS')
1029 self.SConstruct_dir = None
1030 self.max_drift = default_max_drift
1034 self.pathTop = os.getcwd()
1037 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1039 self.Top = self.Dir(self.pathTop)
1041 self.Top.tpath = '.'
1042 self._cwd = self.Top
1044 DirNodeInfo.fs = self
1045 FileNodeInfo.fs = self
1047 def set_SConstruct_dir(self, dir):
1048 self.SConstruct_dir = dir
1050 def get_max_drift(self):
1051 return self.max_drift
1053 def set_max_drift(self, max_drift):
1054 self.max_drift = max_drift
1059 def chdir(self, dir, change_os_dir=0):
1060 """Change the current working directory for lookups.
1061 If change_os_dir is true, we will also change the "real" cwd
1069 os.chdir(dir.abspath)
1074 def get_root(self, drive):
1076 Returns the root directory for the specified drive, creating
1079 drive = _my_normcase(drive)
1081 return self.Root[drive]
1083 root = RootDir(drive, self)
1084 self.Root[drive] = root
1086 self.Root[self.defaultDrive] = root
1087 elif drive == self.defaultDrive:
1088 self.Root[''] = root
1091 def _lookup(self, p, directory, fsclass, create=1):
1093 The generic entry point for Node lookup with user-supplied data.
1095 This translates arbitrary input into a canonical Node.FS object
1096 of the specified fsclass. The general approach for strings is
1097 to turn it into a fully normalized absolute path and then call
1098 the root directory's lookup_abs() method for the heavy lifting.
1100 If the path name begins with '#', it is unconditionally
1101 interpreted relative to the top-level directory of this FS. '#'
1102 is treated as a synonym for the top-level SConstruct directory,
1103 much like '~' is treated as a synonym for the user's home
1104 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1105 to the 'foo' subdirectory underneath the top-level SConstruct
1108 If the path name is relative, then the path is looked up relative
1109 to the specified directory, or the current directory (self._cwd,
1110 typically the SConscript directory) if the specified directory
1113 if isinstance(p, Base):
1114 # It's already a Node.FS object. Make sure it's the right
1116 p.must_be_same(fsclass)
1118 # str(p) in case it's something like a proxy object
1121 initial_hash = (p[0:1] == '#')
1123 # There was an initial '#', so we strip it and override
1124 # whatever directory they may have specified with the
1125 # top-level SConstruct directory.
1127 directory = self.Top
1129 if directory and not isinstance(directory, Dir):
1130 directory = self.Dir(directory)
1133 drive, p = os.path.splitdrive(p)
1137 # This causes a naked drive letter to be treated as a synonym
1138 # for the root directory on that drive.
1140 absolute = os.path.isabs(p)
1142 needs_normpath = needs_normpath_check.match(p)
1144 if initial_hash or not absolute:
1145 # This is a relative lookup, either to the top-level
1146 # SConstruct directory (because of the initial '#') or to
1147 # the current directory (the path name is not absolute).
1148 # Add the string to the appropriate directory lookup path,
1149 # after which the whole thing gets normalized.
1151 directory = self._cwd
1153 p = directory.labspath + '/' + p
1155 p = directory.labspath
1158 p = os.path.normpath(p)
1160 if drive or absolute:
1161 root = self.get_root(drive)
1164 directory = self._cwd
1165 root = directory.root
1168 p = string.replace(p, os.sep, '/')
1169 return root._lookup_abs(p, fsclass, create)
1171 def Entry(self, name, directory = None, create = 1):
1172 """Lookup or create a generic Entry node with the specified name.
1173 If the name is a relative path (begins with ./, ../, or a file
1174 name), then it is looked up relative to the supplied directory
1175 node, or to the top level directory of the FS (supplied at
1176 construction time) if no directory is supplied.
1178 return self._lookup(name, directory, Entry, create)
1180 def File(self, name, directory = None, create = 1):
1181 """Lookup or create a File node with the specified name. If
1182 the name is a relative path (begins with ./, ../, or a file name),
1183 then it is looked up relative to the supplied directory node,
1184 or to the top level directory of the FS (supplied at construction
1185 time) if no directory is supplied.
1187 This method will raise TypeError if a directory is found at the
1190 return self._lookup(name, directory, File, create)
1192 def Dir(self, name, directory = None, create = True):
1193 """Lookup or create a Dir node with the specified name. If
1194 the name is a relative path (begins with ./, ../, or a file name),
1195 then it is looked up relative to the supplied directory node,
1196 or to the top level directory of the FS (supplied at construction
1197 time) if no directory is supplied.
1199 This method will raise TypeError if a normal file is found at the
1202 return self._lookup(name, directory, Dir, create)
1204 def VariantDir(self, variant_dir, src_dir, duplicate=1):
1205 """Link the supplied variant directory to the source directory
1206 for purposes of building files."""
1208 if not isinstance(src_dir, SCons.Node.Node):
1209 src_dir = self.Dir(src_dir)
1210 if not isinstance(variant_dir, SCons.Node.Node):
1211 variant_dir = self.Dir(variant_dir)
1212 if src_dir.is_under(variant_dir):
1213 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1214 if variant_dir.srcdir:
1215 if variant_dir.srcdir == src_dir:
1216 return # We already did this.
1217 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1218 variant_dir.link(src_dir, duplicate)
1220 def Repository(self, *dirs):
1221 """Specify Repository directories to search."""
1223 if not isinstance(d, SCons.Node.Node):
1225 self.Top.addRepository(d)
1227 def variant_dir_target_climb(self, orig, dir, tail):
1228 """Create targets in corresponding variant directories
1230 Climb the directory tree, and look up path names
1231 relative to any linked variant directories we find.
1233 Even though this loops and walks up the tree, we don't memoize
1234 the return value because this is really only used to process
1235 the command-line targets.
1239 fmt = "building associated VariantDir targets: %s"
1242 for bd in dir.variant_dirs:
1243 if start_dir.is_under(bd):
1244 # If already in the build-dir location, don't reflect
1245 return [orig], fmt % str(orig)
1246 p = apply(os.path.join, [bd.path] + tail)
1247 targets.append(self.Entry(p))
1248 tail = [dir.name] + tail
1251 message = fmt % string.join(map(str, targets))
1252 return targets, message
1254 def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1258 This is mainly a shim layer
1262 return cwd.glob(pathname, ondisk, source, strings)
1264 class DirNodeInfo(SCons.Node.NodeInfoBase):
1265 # This should get reset by the FS initialization.
1266 current_version_id = 1
1270 def str_to_node(self, s):
1274 drive, s = os.path.splitdrive(s)
1276 root = self.fs.get_root(drive)
1277 if not os.path.isabs(s):
1278 s = top.labspath + '/' + s
1279 return root._lookup_abs(s, Entry)
1281 class DirBuildInfo(SCons.Node.BuildInfoBase):
1282 current_version_id = 1
1284 glob_magic_check = re.compile('[*?[]')
1286 def has_glob_magic(s):
1287 return glob_magic_check.search(s) is not None
1290 """A class for directories in a file system.
1293 memoizer_counters = []
1295 NodeInfo = DirNodeInfo
1296 BuildInfo = DirBuildInfo
1298 def __init__(self, name, directory, fs):
1299 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1300 Base.__init__(self, name, directory, fs)
1304 """Turn a file system Node (either a freshly initialized directory
1305 object or a separate Entry object) into a proper directory object.
1307 Set up this directory's entries and hook it into the file
1308 system tree. Specify that directories (this Node) don't use
1309 signatures for calculating whether they're current.
1312 self.repositories = []
1316 self.entries['.'] = self
1317 self.entries['..'] = self.dir
1320 self._sconsign = None
1321 self.variant_dirs = []
1322 self.root = self.dir.root
1324 # Don't just reset the executor, replace its action list,
1325 # because it might have some pre-or post-actions that need to
1327 self.builder = get_MkdirBuilder()
1328 self.get_executor().set_action_list(self.builder.action)
1330 def diskcheck_match(self):
1331 diskcheck_match(self, self.isfile,
1332 "File %s found where directory expected.")
1334 def __clearRepositoryCache(self, duplicate=None):
1335 """Called when we change the repository(ies) for a directory.
1336 This clears any cached information that is invalidated by changing
1339 for node in self.entries.values():
1340 if node != self.dir:
1341 if node != self and isinstance(node, Dir):
1342 node.__clearRepositoryCache(duplicate)
1347 except AttributeError:
1349 if duplicate != None:
1350 node.duplicate=duplicate
1352 def __resetDuplicate(self, node):
1354 node.duplicate = node.get_dir().duplicate
1356 def Entry(self, name):
1358 Looks up or creates an entry node named 'name' relative to
1361 return self.fs.Entry(name, self)
1363 def Dir(self, name, create=True):
1365 Looks up or creates a directory node named 'name' relative to
1368 dir = self.fs.Dir(name, self, create)
1371 def File(self, name):
1373 Looks up or creates a file node named 'name' relative to
1376 return self.fs.File(name, self)
1378 def _lookup_rel(self, name, klass, create=1):
1380 Looks up a *normalized* relative path name, relative to this
1383 This method is intended for use by internal lookups with
1384 already-normalized path data. For general-purpose lookups,
1385 use the Entry(), Dir() and File() methods above.
1387 This method does *no* input checking and will die or give
1388 incorrect results if it's passed a non-normalized path name (e.g.,
1389 a path containing '..'), an absolute path name, a top-relative
1390 ('#foo') path name, or any kind of object.
1392 name = self.entry_labspath(name)
1393 return self.root._lookup_abs(name, klass, create)
1395 def link(self, srcdir, duplicate):
1396 """Set this directory as the variant directory for the
1397 supplied source directory."""
1398 self.srcdir = srcdir
1399 self.duplicate = duplicate
1400 self.__clearRepositoryCache(duplicate)
1401 srcdir.variant_dirs.append(self)
1403 def getRepositories(self):
1404 """Returns a list of repositories for this directory.
1406 if self.srcdir and not self.duplicate:
1407 return self.srcdir.get_all_rdirs() + self.repositories
1408 return self.repositories
1410 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1412 def get_all_rdirs(self):
1414 return list(self._memo['get_all_rdirs'])
1422 for rep in dir.getRepositories():
1423 result.append(rep.Dir(fname))
1427 fname = dir.name + os.sep + fname
1430 self._memo['get_all_rdirs'] = list(result)
1434 def addRepository(self, dir):
1435 if dir != self and not dir in self.repositories:
1436 self.repositories.append(dir)
1438 self.__clearRepositoryCache()
1441 return self.entries['..']
1443 def _rel_path_key(self, other):
1446 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1448 def rel_path(self, other):
1449 """Return a path to "other" relative to this directory.
1452 # This complicated and expensive method, which constructs relative
1453 # paths between arbitrary Node.FS objects, is no longer used
1454 # by SCons itself. It was introduced to store dependency paths
1455 # in .sconsign files relative to the target, but that ended up
1456 # being significantly inefficient.
1458 # We're continuing to support the method because some SConstruct
1459 # files out there started using it when it was available, and
1460 # we're all about backwards compatibility..
1463 memo_dict = self._memo['rel_path']
1466 self._memo['rel_path'] = memo_dict
1469 return memo_dict[other]
1477 elif not other in self.path_elements:
1480 other_dir = other.get_dir()
1481 except AttributeError:
1484 if other_dir is None:
1487 dir_rel_path = self.rel_path(other_dir)
1488 if dir_rel_path == '.':
1491 result = dir_rel_path + os.sep + other.name
1495 i = self.path_elements.index(other) + 1
1497 path_elems = ['..'] * (len(self.path_elements) - i) \
1498 + map(lambda n: n.name, other.path_elements[i:])
1500 result = string.join(path_elems, os.sep)
1502 memo_dict[other] = result
1506 def get_env_scanner(self, env, kw={}):
1507 import SCons.Defaults
1508 return SCons.Defaults.DirEntryScanner
1510 def get_target_scanner(self):
1511 import SCons.Defaults
1512 return SCons.Defaults.DirEntryScanner
1514 def get_found_includes(self, env, scanner, path):
1515 """Return this directory's implicit dependencies.
1517 We don't bother caching the results because the scan typically
1518 shouldn't be requested more than once (as opposed to scanning
1519 .h file contents, which can be requested as many times as the
1520 files is #included by other files).
1524 # Clear cached info for this Dir. If we already visited this
1525 # directory on our walk down the tree (because we didn't know at
1526 # that point it was being used as the source for another Node)
1527 # then we may have calculated build signature before realizing
1528 # we had to scan the disk. Now that we have to, though, we need
1529 # to invalidate the old calculated signature so that any node
1530 # dependent on our directory structure gets one that includes
1531 # info about everything on disk.
1533 return scanner(self, env, path)
1536 # Taskmaster interface subsystem
1542 def build(self, **kw):
1543 """A null "builder" for directories."""
1545 if not self.builder is MkdirBuilder:
1546 apply(SCons.Node.Node.build, [self,], kw)
1553 """Create this directory, silently and without worrying about
1554 whether the builder is the default or not."""
1560 listDirs.append(parent)
1563 raise SCons.Errors.StopError, parent.path
1566 for dirnode in listDirs:
1568 # Don't call dirnode.build(), call the base Node method
1569 # directly because we definitely *must* create this
1570 # directory. The dirnode.build() method will suppress
1571 # the build if it's the default builder.
1572 SCons.Node.Node.build(dirnode)
1573 dirnode.get_executor().nullify()
1574 # The build() action may or may not have actually
1575 # created the directory, depending on whether the -n
1576 # option was used or not. Delete the _exists and
1577 # _rexists attributes so they can be reevaluated.
1582 def multiple_side_effect_has_builder(self):
1584 return not self.builder is MkdirBuilder and self.has_builder()
1586 def alter_targets(self):
1587 """Return any corresponding targets in a variant directory.
1589 return self.fs.variant_dir_target_climb(self, self, [])
1591 def scanner_key(self):
1592 """A directory does not get scanned."""
1595 def get_contents(self):
1596 """Return aggregate contents of all our children."""
1597 contents = map(lambda n: n.get_contents(), self.children())
1598 return string.join(contents, '')
1600 def do_duplicate(self, src):
1603 changed_since_last_build = SCons.Node.Node.state_has_changed
1605 def is_up_to_date(self):
1606 """If any child is not up-to-date, then this directory isn't,
1608 if not self.builder is MkdirBuilder and not self.exists():
1610 up_to_date = SCons.Node.up_to_date
1611 for kid in self.children():
1612 if kid.get_state() > up_to_date:
1617 if not self.exists():
1618 norm_name = _my_normcase(self.name)
1619 for dir in self.dir.get_all_rdirs():
1620 try: node = dir.entries[norm_name]
1621 except KeyError: node = dir.dir_on_disk(self.name)
1622 if node and node.exists() and \
1623 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1628 """Return the .sconsign file info for this directory,
1629 creating it first if necessary."""
1630 if not self._sconsign:
1631 import SCons.SConsign
1632 self._sconsign = SCons.SConsign.ForDirectory(self)
1633 return self._sconsign
1636 """Dir has a special need for srcnode()...if we
1637 have a srcdir attribute set, then that *is* our srcnode."""
1640 return Base.srcnode(self)
1642 def get_timestamp(self):
1643 """Return the latest timestamp from among our children"""
1645 for kid in self.children():
1646 if kid.get_timestamp() > stamp:
1647 stamp = kid.get_timestamp()
1650 def entry_abspath(self, name):
1651 return self.abspath + os.sep + name
1653 def entry_labspath(self, name):
1654 return self.labspath + '/' + name
1656 def entry_path(self, name):
1657 return self.path + os.sep + name
1659 def entry_tpath(self, name):
1660 return self.tpath + os.sep + name
1662 def entry_exists_on_disk(self, name):
1664 d = self.on_disk_entries
1665 except AttributeError:
1668 entries = os.listdir(self.abspath)
1672 for entry in map(_my_normcase, entries):
1674 self.on_disk_entries = d
1675 return d.has_key(_my_normcase(name))
1677 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1679 def srcdir_list(self):
1681 return self._memo['srcdir_list']
1691 result.append(dir.srcdir.Dir(dirname))
1692 dirname = dir.name + os.sep + dirname
1695 self._memo['srcdir_list'] = result
1699 def srcdir_duplicate(self, name):
1700 for dir in self.srcdir_list():
1701 if self.is_under(dir):
1702 # We shouldn't source from something in the build path;
1703 # variant_dir is probably under src_dir, in which case
1704 # we are reflecting.
1706 if dir.entry_exists_on_disk(name):
1707 srcnode = dir.Entry(name).disambiguate()
1709 node = self.Entry(name).disambiguate()
1710 node.do_duplicate(srcnode)
1716 def _srcdir_find_file_key(self, filename):
1719 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1721 def srcdir_find_file(self, filename):
1723 memo_dict = self._memo['srcdir_find_file']
1726 self._memo['srcdir_find_file'] = memo_dict
1729 return memo_dict[filename]
1734 if (isinstance(node, File) or isinstance(node, Entry)) and \
1735 (node.is_derived() or node.exists()):
1739 norm_name = _my_normcase(filename)
1741 for rdir in self.get_all_rdirs():
1742 try: node = rdir.entries[norm_name]
1743 except KeyError: node = rdir.file_on_disk(filename)
1744 else: node = func(node)
1746 result = (node, self)
1747 memo_dict[filename] = result
1750 for srcdir in self.srcdir_list():
1751 for rdir in srcdir.get_all_rdirs():
1752 try: node = rdir.entries[norm_name]
1753 except KeyError: node = rdir.file_on_disk(filename)
1754 else: node = func(node)
1756 result = (File(filename, self, self.fs), srcdir)
1757 memo_dict[filename] = result
1760 result = (None, None)
1761 memo_dict[filename] = result
1764 def dir_on_disk(self, name):
1765 if self.entry_exists_on_disk(name):
1766 try: return self.Dir(name)
1767 except TypeError: pass
1770 def file_on_disk(self, name):
1771 if self.entry_exists_on_disk(name) or \
1772 diskcheck_rcs(self, name) or \
1773 diskcheck_sccs(self, name):
1774 try: return self.File(name)
1775 except TypeError: pass
1776 node = self.srcdir_duplicate(name)
1777 if isinstance(node, Dir):
1781 def walk(self, func, arg):
1783 Walk this directory tree by calling the specified function
1784 for each directory in the tree.
1786 This behaves like the os.path.walk() function, but for in-memory
1787 Node.FS.Dir objects. The function takes the same arguments as
1788 the functions passed to os.path.walk():
1790 func(arg, dirname, fnames)
1792 Except that "dirname" will actually be the directory *Node*,
1793 not the string. The '.' and '..' entries are excluded from
1794 fnames. The fnames list may be modified in-place to filter the
1795 subdirectories visited or otherwise impose a specific order.
1796 The "arg" argument is always passed to func() and may be used
1797 in any way (or ignored, passing None is common).
1799 entries = self.entries
1800 names = entries.keys()
1803 func(arg, self, names)
1804 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1805 for dirname in filter(select_dirs, names):
1806 entries[dirname].walk(func, arg)
1808 def glob(self, pathname, ondisk=True, source=False, strings=False):
1810 Returns a list of Nodes (or strings) matching a specified
1813 Pathname patterns follow UNIX shell semantics: * matches
1814 any-length strings of any characters, ? matches any character,
1815 and [] can enclose lists or ranges of characters. Matches do
1816 not span directory separators.
1818 The matches take into account Repositories, returning local
1819 Nodes if a corresponding entry exists in a Repository (either
1820 an in-memory Node or something on disk).
1822 By defafult, the glob() function matches entries that exist
1823 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1824 argument to False (or some other non-true value) causes the glob()
1825 function to only match in-memory Nodes. The default behavior is
1826 to return both the on-disk and in-memory Nodes.
1828 The "source" argument, when true, specifies that corresponding
1829 source Nodes must be returned if you're globbing in a build
1830 directory (initialized with VariantDir()). The default behavior
1831 is to return Nodes local to the VariantDir().
1833 The "strings" argument, when true, returns the matches as strings,
1834 not Nodes. The strings are path names relative to this directory.
1836 The underlying algorithm is adapted from the glob.glob() function
1837 in the Python library (but heavily modified), and uses fnmatch()
1840 dirname, basename = os.path.split(pathname)
1842 return self._glob1(basename, ondisk, source, strings)
1843 if has_glob_magic(dirname):
1844 list = self.glob(dirname, ondisk, source, strings=False)
1846 list = [self.Dir(dirname, create=True)]
1849 r = dir._glob1(basename, ondisk, source, strings)
1851 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1855 def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1857 Globs for and returns a list of entry names matching a single
1858 pattern in this directory.
1860 This searches any repositories and source directories for
1861 corresponding entries and returns a Node (or string) relative
1862 to the current directory if an entry is found anywhere.
1864 TODO: handle pattern with no wildcard
1866 search_dir_list = self.get_all_rdirs()
1867 for srcdir in self.srcdir_list():
1868 search_dir_list.extend(srcdir.get_all_rdirs())
1871 for dir in search_dir_list:
1872 # We use the .name attribute from the Node because the keys of
1873 # the dir.entries dictionary are normalized (that is, all upper
1874 # case) on case-insensitive systems like Windows.
1875 #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
1876 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1877 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1878 names.extend(node_names)
1881 disk_names = os.listdir(dir.abspath)
1885 names.extend(disk_names)
1887 # We're going to return corresponding Nodes in
1888 # the local directory, so we need to make sure
1889 # those Nodes exist. We only want to create
1890 # Nodes for the entries that will match the
1891 # specified pattern, though, which means we
1892 # need to filter the list here, even though
1893 # the overall list will also be filtered later,
1894 # after we exit this loop.
1895 if pattern[0] != '.':
1896 #disk_names = [ d for d in disk_names if d[0] != '.' ]
1897 disk_names = filter(lambda x: x[0] != '.', disk_names)
1898 disk_names = fnmatch.filter(disk_names, pattern)
1899 rep_nodes = map(dir.Entry, disk_names)
1900 #rep_nodes = [ n.disambiguate() for n in rep_nodes ]
1901 rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
1902 for node, name in izip(rep_nodes, disk_names):
1903 n = self.Entry(name)
1904 if n.__class__ != node.__class__:
1905 n.__class__ = node.__class__
1909 if pattern[0] != '.':
1910 #names = [ n for n in names if n[0] != '.' ]
1911 names = filter(lambda x: x[0] != '.', names)
1912 names = fnmatch.filter(names, pattern)
1917 #return [ self.entries[_my_normcase(n)] for n in names ]
1918 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1921 """A class for the root directory of a file system.
1923 This is the same as a Dir class, except that the path separator
1924 ('/' or '\\') is actually part of the name, so we don't need to
1925 add a separator when creating the path names of entries within
1928 def __init__(self, name, fs):
1929 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1930 # We're going to be our own parent directory (".." entry and .dir
1931 # attribute) so we have to set up some values so Base.__init__()
1932 # won't gag won't it calls some of our methods.
1937 self.path_elements = []
1940 Base.__init__(self, name, self, fs)
1942 # Now set our paths to what we really want them to be: the
1943 # initial drive letter (the name) plus the directory separator,
1944 # except for the "lookup abspath," which does not have the
1946 self.abspath = name + os.sep
1948 self.path = name + os.sep
1949 self.tpath = name + os.sep
1952 self._lookupDict = {}
1954 # The // and os.sep + os.sep entries are necessary because
1955 # os.path.normpath() seems to preserve double slashes at the
1956 # beginning of a path (presumably for UNC path names), but
1957 # collapses triple slashes to a single slash.
1958 self._lookupDict[''] = self
1959 self._lookupDict['/'] = self
1960 self._lookupDict['//'] = self
1961 self._lookupDict[os.sep] = self
1962 self._lookupDict[os.sep + os.sep] = self
1964 def must_be_same(self, klass):
1967 Base.must_be_same(self, klass)
1969 def _lookup_abs(self, p, klass, create=1):
1971 Fast (?) lookup of a *normalized* absolute path.
1973 This method is intended for use by internal lookups with
1974 already-normalized path data. For general-purpose lookups,
1975 use the FS.Entry(), FS.Dir() or FS.File() methods.
1977 The caller is responsible for making sure we're passed a
1978 normalized absolute path; we merely let Python's dictionary look
1979 up and return the One True Node.FS object for the path.
1981 If no Node for the specified "p" doesn't already exist, and
1982 "create" is specified, the Node may be created after recursive
1983 invocation to find or create the parent directory or directories.
1987 result = self._lookupDict[k]
1990 raise SCons.Errors.UserError
1991 # There is no Node for this path name, and we're allowed
1993 dir_name, file_name = os.path.split(p)
1994 dir_node = self._lookup_abs(dir_name, Dir)
1995 result = klass(file_name, dir_node, self.fs)
1996 self._lookupDict[k] = result
1997 dir_node.entries[_my_normcase(file_name)] = result
1998 dir_node.implicit = None
2000 # Double-check on disk (as configured) that the Node we
2001 # created matches whatever is out there in the real world.
2002 result.diskcheck_match()
2004 # There is already a Node for this path name. Allow it to
2005 # complain if we were looking for an inappropriate type.
2006 result.must_be_same(klass)
2012 def entry_abspath(self, name):
2013 return self.abspath + name
2015 def entry_labspath(self, name):
2018 def entry_path(self, name):
2019 return self.path + name
2021 def entry_tpath(self, name):
2022 return self.tpath + name
2024 def is_under(self, dir):
2036 def src_builder(self):
2039 class FileNodeInfo(SCons.Node.NodeInfoBase):
2040 current_version_id = 1
2042 field_list = ['csig', 'timestamp', 'size']
2044 # This should get reset by the FS initialization.
2047 def str_to_node(self, s):
2051 drive, s = os.path.splitdrive(s)
2053 root = self.fs.get_root(drive)
2054 if not os.path.isabs(s):
2055 s = top.labspath + '/' + s
2056 return root._lookup_abs(s, Entry)
2058 class FileBuildInfo(SCons.Node.BuildInfoBase):
2059 current_version_id = 1
2061 def convert_to_sconsign(self):
2063 Converts this FileBuildInfo object for writing to a .sconsign file
2065 This replaces each Node in our various dependency lists with its
2066 usual string representation: relative to the top-level SConstruct
2067 directory, or an absolute path if it's outside.
2075 except AttributeError:
2078 s = string.replace(s, os.sep, '/')
2080 for attr in ['bsources', 'bdepends', 'bimplicit']:
2082 val = getattr(self, attr)
2083 except AttributeError:
2086 setattr(self, attr, map(node_to_str, val))
2087 def convert_from_sconsign(self, dir, name):
2089 Converts a newly-read FileBuildInfo object for in-SCons use
2091 For normal up-to-date checking, we don't have any conversion to
2092 perform--but we're leaving this method here to make that clear.
2095 def prepare_dependencies(self):
2097 Prepares a FileBuildInfo object for explaining what changed
2099 The bsources, bdepends and bimplicit lists have all been
2100 stored on disk as paths relative to the top-level SConstruct
2101 directory. Convert the strings to actual Nodes (for use by the
2102 --debug=explain code and --implicit-cache).
2105 ('bsources', 'bsourcesigs'),
2106 ('bdepends', 'bdependsigs'),
2107 ('bimplicit', 'bimplicitsigs'),
2109 for (nattr, sattr) in attrs:
2111 strings = getattr(self, nattr)
2112 nodeinfos = getattr(self, sattr)
2113 except AttributeError:
2117 for s, ni in izip(strings, nodeinfos):
2118 if not isinstance(s, SCons.Node.Node):
2119 s = ni.str_to_node(s)
2121 setattr(self, nattr, nodes)
2122 def format(self, names=0):
2124 bkids = self.bsources + self.bdepends + self.bimplicit
2125 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
2126 for bkid, bkidsig in izip(bkids, bkidsigs):
2127 result.append(str(bkid) + ': ' +
2128 string.join(bkidsig.format(names=names), ' '))
2129 result.append('%s [%s]' % (self.bactsig, self.bact))
2130 return string.join(result, '\n')
2133 """A class for files in a file system.
2136 memoizer_counters = []
2138 NodeInfo = FileNodeInfo
2139 BuildInfo = FileBuildInfo
2141 def diskcheck_match(self):
2142 diskcheck_match(self, self.isdir,
2143 "Directory %s found where file expected.")
2145 def __init__(self, name, directory, fs):
2146 if __debug__: logInstanceCreation(self, 'Node.FS.File')
2147 Base.__init__(self, name, directory, fs)
2150 def Entry(self, name):
2151 """Create an entry node named 'name' relative to
2152 the SConscript directory of this file."""
2153 cwd = self.cwd or self.fs._cwd
2154 return cwd.Entry(name)
2156 def Dir(self, name, create=True):
2157 """Create a directory node named 'name' relative to
2158 the SConscript directory of this file."""
2159 cwd = self.cwd or self.fs._cwd
2160 return cwd.Dir(name, create)
2162 def Dirs(self, pathlist):
2163 """Create a list of directories relative to the SConscript
2164 directory of this file."""
2165 return map(lambda p, s=self: s.Dir(p), pathlist)
2167 def File(self, name):
2168 """Create a file node named 'name' relative to
2169 the SConscript directory of this file."""
2170 cwd = self.cwd or self.fs._cwd
2171 return cwd.File(name)
2173 #def generate_build_dict(self):
2174 # """Return an appropriate dictionary of values for building
2176 # return {'Dir' : self.Dir,
2177 # 'File' : self.File,
2178 # 'RDirs' : self.RDirs}
2181 """Turn a file system node into a File object."""
2182 self.scanner_paths = {}
2183 if not hasattr(self, '_local'):
2186 # If there was already a Builder set on this entry, then
2187 # we need to make sure we call the target-decider function,
2188 # not the source-decider. Reaching in and doing this by hand
2189 # is a little bogus. We'd prefer to handle this by adding
2190 # an Entry.builder_set() method that disambiguates like the
2191 # other methods, but that starts running into problems with the
2192 # fragile way we initialize Dir Nodes with their Mkdir builders,
2193 # yet still allow them to be overridden by the user. Since it's
2194 # not clear right now how to fix that, stick with what works
2195 # until it becomes clear...
2196 if self.has_builder():
2197 self.changed_since_last_build = self.decide_target
2199 def scanner_key(self):
2200 return self.get_suffix()
2202 def get_contents(self):
2203 if not self.rexists():
2205 fname = self.rfile().abspath
2207 r = open(fname, "rb").read()
2208 except EnvironmentError, e:
2214 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2218 return self._memo['get_size']
2223 size = self.rfile().getsize()
2227 self._memo['get_size'] = size
2231 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2233 def get_timestamp(self):
2235 return self._memo['get_timestamp']
2240 timestamp = self.rfile().getmtime()
2244 self._memo['get_timestamp'] = timestamp
2248 def store_info(self):
2249 # Merge our build information into the already-stored entry.
2250 # This accomodates "chained builds" where a file that's a target
2251 # in one build (SConstruct file) is a source in a different build.
2252 # See test/chained-build.py for the use case.
2253 self.dir.sconsign().store_info(self.name, self)
2255 convert_copy_attrs = [
2265 convert_sig_attrs = [
2271 def convert_old_entry(self, old_entry):
2272 # Convert a .sconsign entry from before the Big Signature
2273 # Refactoring, doing what we can to convert its information
2274 # to the new .sconsign entry format.
2276 # The old format looked essentially like this:
2285 # .bsourcesigs ("signature" list)
2287 # .bdependsigs ("signature" list)
2289 # .bimplicitsigs ("signature" list)
2293 # The new format looks like this:
2300 # .binfo (BuildInfo)
2302 # .bsourcesigs (NodeInfo list)
2308 # .bdependsigs (NodeInfo list)
2314 # .bimplicitsigs (NodeInfo list)
2322 # The basic idea of the new structure is that a NodeInfo always
2323 # holds all available information about the state of a given Node
2324 # at a certain point in time. The various .b*sigs lists can just
2325 # be a list of pointers to the .ninfo attributes of the different
2326 # dependent nodes, without any copying of information until it's
2327 # time to pickle it for writing out to a .sconsign file.
2329 # The complicating issue is that the *old* format only stored one
2330 # "signature" per dependency, based on however the *last* build
2331 # was configured. We don't know from just looking at it whether
2332 # it was a build signature, a content signature, or a timestamp
2333 # "signature". Since we no longer use build signatures, the
2334 # best we can do is look at the length and if it's thirty two,
2335 # assume that it was (or might have been) a content signature.
2336 # If it was actually a build signature, then it will cause a
2337 # rebuild anyway when it doesn't match the new content signature,
2338 # but that's probably the best we can do.
2339 import SCons.SConsign
2340 new_entry = SCons.SConsign.SConsignEntry()
2341 new_entry.binfo = self.new_binfo()
2342 binfo = new_entry.binfo
2343 for attr in self.convert_copy_attrs:
2345 value = getattr(old_entry, attr)
2346 except AttributeError:
2349 setattr(binfo, attr, value)
2350 delattr(old_entry, attr)
2351 for attr in self.convert_sig_attrs:
2353 sig_list = getattr(old_entry, attr)
2354 except AttributeError:
2358 for sig in sig_list:
2359 ninfo = self.new_ninfo()
2363 ninfo.timestamp = sig
2365 setattr(binfo, attr, value)
2366 delattr(old_entry, attr)
2369 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2371 def get_stored_info(self):
2373 return self._memo['get_stored_info']
2378 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2379 except (KeyError, OSError):
2380 import SCons.SConsign
2381 sconsign_entry = SCons.SConsign.SConsignEntry()
2382 sconsign_entry.binfo = self.new_binfo()
2383 sconsign_entry.ninfo = self.new_ninfo()
2385 if isinstance(sconsign_entry, FileBuildInfo):
2386 # This is a .sconsign file from before the Big Signature
2387 # Refactoring; convert it as best we can.
2388 sconsign_entry = self.convert_old_entry(sconsign_entry)
2390 delattr(sconsign_entry.ninfo, 'bsig')
2391 except AttributeError:
2394 self._memo['get_stored_info'] = sconsign_entry
2396 return sconsign_entry
2398 def get_stored_implicit(self):
2399 binfo = self.get_stored_info().binfo
2400 binfo.prepare_dependencies()
2401 try: return binfo.bimplicit
2402 except AttributeError: return None
2404 def rel_path(self, other):
2405 return self.dir.rel_path(other)
2407 def _get_found_includes_key(self, env, scanner, path):
2408 return (id(env), id(scanner), path)
2410 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2412 def get_found_includes(self, env, scanner, path):
2413 """Return the included implicit dependencies in this file.
2414 Cache results so we only scan the file once per path
2415 regardless of how many times this information is requested.
2417 memo_key = (id(env), id(scanner), path)
2419 memo_dict = self._memo['get_found_includes']
2422 self._memo['get_found_includes'] = memo_dict
2425 return memo_dict[memo_key]
2430 result = scanner(self, env, path)
2431 result = map(lambda N: N.disambiguate(), result)
2435 memo_dict[memo_key] = result
2439 def _createDir(self):
2440 # ensure that the directories for this node are
2444 def retrieve_from_cache(self):
2445 """Try to retrieve the node's content from a cache
2447 This method is called from multiple threads in a parallel build,
2448 so only do thread safe stuff here. Do thread unsafe stuff in
2451 Returns true iff the node was successfully retrieved.
2455 if not self.is_derived():
2457 return self.get_build_env().get_CacheDir().retrieve(self)
2461 Called just after this node is successfully built.
2463 # Push this file out to cache before the superclass Node.built()
2464 # method has a chance to clear the build signature, which it
2465 # will do if this file has a source scanner.
2467 # We have to clear the memoized values *before* we push it to
2468 # cache so that the memoization of the self.exists() return
2469 # value doesn't interfere.
2470 self.clear_memoized_values()
2472 self.get_build_env().get_CacheDir().push(self)
2473 SCons.Node.Node.built(self)
2477 self.get_build_env().get_CacheDir().push_if_forced(self)
2479 ninfo = self.get_ninfo()
2481 csig = self.get_max_drift_csig()
2485 ninfo.timestamp = self.get_timestamp()
2486 ninfo.size = self.get_size()
2488 if not self.has_builder():
2489 # This is a source file, but it might have been a target file
2490 # in another build that included more of the DAG. Copy
2491 # any build information that's stored in the .sconsign file
2492 # into our binfo object so it doesn't get lost.
2493 old = self.get_stored_info()
2494 self.get_binfo().__dict__.update(old.binfo.__dict__)
2498 def find_src_builder(self):
2501 scb = self.dir.src_builder()
2503 if diskcheck_sccs(self.dir, self.name):
2504 scb = get_DefaultSCCSBuilder()
2505 elif diskcheck_rcs(self.dir, self.name):
2506 scb = get_DefaultRCSBuilder()
2512 except AttributeError:
2515 self.builder_set(scb)
2518 def has_src_builder(self):
2519 """Return whether this Node has a source builder or not.
2521 If this Node doesn't have an explicit source code builder, this
2522 is where we figure out, on the fly, if there's a transparent
2523 source code builder for it.
2525 Note that if we found a source builder, we also set the
2526 self.builder attribute, so that all of the methods that actually
2527 *build* this file don't have to do anything different.
2531 except AttributeError:
2532 scb = self.sbuilder = self.find_src_builder()
2533 return not scb is None
2535 def alter_targets(self):
2536 """Return any corresponding targets in a variant directory.
2538 if self.is_derived():
2540 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2542 def _rmv_existing(self):
2543 self.clear_memoized_values()
2544 e = Unlink(self, [], None)
2545 if isinstance(e, SCons.Errors.BuildError):
2549 # Taskmaster interface subsystem
2552 def make_ready(self):
2553 self.has_src_builder()
2557 """Prepare for this file to be created."""
2558 SCons.Node.Node.prepare(self)
2560 if self.get_state() != SCons.Node.up_to_date:
2562 if self.is_derived() and not self.precious:
2563 self._rmv_existing()
2567 except SCons.Errors.StopError, drive:
2568 desc = "No drive `%s' for target `%s'." % (drive, self)
2569 raise SCons.Errors.StopError, desc
2576 """Remove this file."""
2577 if self.exists() or self.islink():
2578 self.fs.unlink(self.path)
2582 def do_duplicate(self, src):
2584 Unlink(self, None, None)
2585 e = Link(self, src, None)
2586 if isinstance(e, SCons.Errors.BuildError):
2587 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2588 raise SCons.Errors.StopError, desc
2590 # The Link() action may or may not have actually
2591 # created the file, depending on whether the -n
2592 # option was used or not. Delete the _exists and
2593 # _rexists attributes so they can be reevaluated.
2596 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2600 return self._memo['exists']
2603 # Duplicate from source path if we are set up to do this.
2604 if self.duplicate and not self.is_derived() and not self.linked:
2605 src = self.srcnode()
2607 # At this point, src is meant to be copied in a variant directory.
2609 if src.abspath != self.abspath:
2611 self.do_duplicate(src)
2612 # Can't return 1 here because the duplication might
2613 # not actually occur if the -n option is being used.
2615 # The source file does not exist. Make sure no old
2616 # copy remains in the variant directory.
2617 if Base.exists(self) or self.islink():
2618 self.fs.unlink(self.path)
2619 # Return None explicitly because the Base.exists() call
2620 # above will have cached its value if the file existed.
2621 self._memo['exists'] = None
2623 result = Base.exists(self)
2624 self._memo['exists'] = result
2628 # SIGNATURE SUBSYSTEM
2631 def get_max_drift_csig(self):
2633 Returns the content signature currently stored for this node
2634 if it's been unmodified longer than the max_drift value, or the
2635 max_drift value is 0. Returns None otherwise.
2637 old = self.get_stored_info()
2638 mtime = self.get_timestamp()
2641 max_drift = self.fs.max_drift
2643 if (time.time() - mtime) > max_drift:
2646 if n.timestamp and n.csig and n.timestamp == mtime:
2648 except AttributeError:
2650 elif max_drift == 0:
2652 csig = old.ninfo.csig
2653 except AttributeError:
2660 Generate a node's content signature, the digested signature
2664 cache - alternate node to use for the signature cache
2665 returns - the content signature
2667 ninfo = self.get_ninfo()
2670 except AttributeError:
2673 csig = self.get_max_drift_csig()
2677 contents = self.get_contents()
2679 # This can happen if there's actually a directory on-disk,
2680 # which can be the case if they've disabled disk checks,
2681 # or if an action with a File target actually happens to
2682 # create a same-named directory by mistake.
2685 csig = SCons.Util.MD5signature(contents)
2692 # DECISION SUBSYSTEM
2695 def builder_set(self, builder):
2696 SCons.Node.Node.builder_set(self, builder)
2697 self.changed_since_last_build = self.decide_target
2699 def changed_content(self, target, prev_ni):
2700 cur_csig = self.get_csig()
2702 return cur_csig != prev_ni.csig
2703 except AttributeError:
2706 def changed_state(self, target, prev_ni):
2707 return (self.state != SCons.Node.up_to_date)
2709 def changed_timestamp_then_content(self, target, prev_ni):
2710 if not self.changed_timestamp_match(target, prev_ni):
2712 self.get_ninfo().csig = prev_ni.csig
2713 except AttributeError:
2716 return self.changed_content(target, prev_ni)
2718 def changed_timestamp_newer(self, target, prev_ni):
2720 return self.get_timestamp() > target.get_timestamp()
2721 except AttributeError:
2724 def changed_timestamp_match(self, target, prev_ni):
2726 return self.get_timestamp() != prev_ni.timestamp
2727 except AttributeError:
2730 def decide_source(self, target, prev_ni):
2731 return target.get_build_env().decide_source(self, target, prev_ni)
2733 def decide_target(self, target, prev_ni):
2734 return target.get_build_env().decide_target(self, target, prev_ni)
2736 # Initialize this Node's decider function to decide_source() because
2737 # every file is a source file until it has a Builder attached...
2738 changed_since_last_build = decide_source
2740 def is_up_to_date(self):
2742 if T: Trace('is_up_to_date(%s):' % self)
2743 if not self.exists():
2744 if T: Trace(' not self.exists():')
2745 # The file doesn't exist locally...
2748 # ...but there is one in a Repository...
2749 if not self.changed(r):
2750 if T: Trace(' changed(%s):' % r)
2751 # ...and it's even up-to-date...
2753 # ...and they'd like a local copy.
2754 e = LocalCopy(self, r, None)
2755 if isinstance(e, SCons.Errors.BuildError):
2761 if T: Trace(' None\n')
2765 if T: Trace(' self.exists(): %s\n' % r)
2768 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2772 return self._memo['rfile']
2776 if not self.exists():
2777 norm_name = _my_normcase(self.name)
2778 for dir in self.dir.get_all_rdirs():
2779 try: node = dir.entries[norm_name]
2780 except KeyError: node = dir.file_on_disk(self.name)
2781 if node and node.exists() and \
2782 (isinstance(node, File) or isinstance(node, Entry) \
2783 or not node.is_derived()):
2786 self._memo['rfile'] = result
2790 return str(self.rfile())
2792 def get_cachedir_csig(self):
2794 Fetch a Node's content signature for purposes of computing
2795 another Node's cachesig.
2797 This is a wrapper around the normal get_csig() method that handles
2798 the somewhat obscure case of using CacheDir with the -n option.
2799 Any files that don't exist would normally be "built" by fetching
2800 them from the cache, but the normal get_csig() method will try
2801 to open up the local file, which doesn't exist because the -n
2802 option meant we didn't actually pull the file from cachedir.
2803 But since the file *does* actually exist in the cachedir, we
2804 can use its contents for the csig.
2807 return self.cachedir_csig
2808 except AttributeError:
2811 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2812 if not self.exists() and cachefile and os.path.exists(cachefile):
2813 contents = open(cachefile, 'rb').read()
2814 self.cachedir_csig = SCons.Util.MD5signature(contents)
2816 self.cachedir_csig = self.get_csig()
2817 return self.cachedir_csig
2819 def get_cachedir_bsig(self):
2821 return self.cachesig
2822 except AttributeError:
2825 # Add the path to the cache signature, because multiple
2826 # targets built by the same action will all have the same
2827 # build signature, and we have to differentiate them somehow.
2828 children = self.children()
2829 sigs = map(lambda n: n.get_cachedir_csig(), children)
2830 executor = self.get_executor()
2831 sigs.append(SCons.Util.MD5signature(executor.get_contents()))
2832 sigs.append(self.path)
2833 self.cachesig = SCons.Util.MD5collect(sigs)
2834 return self.cachesig
2838 def get_default_fs():
2847 if SCons.Memoize.use_memoizer:
2848 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2850 memoizer_counters = []
2855 def filedir_lookup(self, p, fd=None):
2857 A helper method for find_file() that looks up a directory for
2858 a file we're trying to find. This only creates the Dir Node if
2859 it exists on-disk, since if the directory doesn't exist we know
2860 we won't find any files in it... :-)
2862 It would be more compact to just use this as a nested function
2863 with a default keyword argument (see the commented-out version
2864 below), but that doesn't work unless you have nested scopes,
2865 so we define it here just so this work under Python 1.5.2.
2868 fd = self.default_filedir
2869 dir, name = os.path.split(fd)
2870 drive, d = os.path.splitdrive(dir)
2871 if d in ('/', os.sep):
2872 return p.fs.get_root(drive).dir_on_disk(name)
2874 p = self.filedir_lookup(p, dir)
2877 norm_name = _my_normcase(name)
2879 node = p.entries[norm_name]
2881 return p.dir_on_disk(name)
2882 if isinstance(node, Dir):
2884 if isinstance(node, Entry):
2885 node.must_be_same(Dir)
2889 def _find_file_key(self, filename, paths, verbose=None):
2890 return (filename, paths)
2892 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2894 def find_file(self, filename, paths, verbose=None):
2896 find_file(str, [Dir()]) -> [nodes]
2898 filename - a filename to find
2899 paths - a list of directory path *nodes* to search in. Can be
2900 represented as a list, a tuple, or a callable that is
2901 called with no arguments and returns the list or tuple.
2903 returns - the node created from the found file.
2905 Find a node corresponding to either a derived file or a file
2906 that exists already.
2908 Only the first file found is returned, and none is returned
2909 if no file is found.
2911 memo_key = self._find_file_key(filename, paths)
2913 memo_dict = self._memo['find_file']
2916 self._memo['find_file'] = memo_dict
2919 return memo_dict[memo_key]
2924 if not SCons.Util.is_String(verbose):
2925 verbose = "find_file"
2926 if not callable(verbose):
2927 verbose = ' %s: ' % verbose
2928 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2930 verbose = lambda x: x
2932 filedir, filename = os.path.split(filename)
2934 # More compact code that we can't use until we drop
2935 # support for Python 1.5.2:
2937 #def filedir_lookup(p, fd=filedir):
2939 # A helper function that looks up a directory for a file
2940 # we're trying to find. This only creates the Dir Node
2941 # if it exists on-disk, since if the directory doesn't
2942 # exist we know we won't find any files in it... :-)
2944 # dir, name = os.path.split(fd)
2946 # p = filedir_lookup(p, dir)
2949 # norm_name = _my_normcase(name)
2951 # node = p.entries[norm_name]
2953 # return p.dir_on_disk(name)
2954 # if isinstance(node, Dir):
2956 # if isinstance(node, Entry):
2957 # node.must_be_same(Dir)
2959 # if isinstance(node, Dir) or isinstance(node, Entry):
2962 #paths = filter(None, map(filedir_lookup, paths))
2964 self.default_filedir = filedir
2965 paths = filter(None, map(self.filedir_lookup, paths))
2969 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2970 node, d = dir.srcdir_find_file(filename)
2972 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2976 memo_dict[memo_key] = result
2980 find_file = FileFinder().find_file