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__"
48 from SCons.Debug import logInstanceCreation
56 # The max_drift value: by default, use a cached signature value for
57 # any file that's been untouched for more than two days.
58 default_max_drift = 2*24*60*60
61 # We stringify these file system Nodes a lot. Turning a file system Node
62 # into a string is non-trivial, because the final string representation
63 # can depend on a lot of factors: whether it's a derived target or not,
64 # whether it's linked to a repository or source directory, and whether
65 # there's duplication going on. The normal technique for optimizing
66 # calculations like this is to memoize (cache) the string value, so you
67 # only have to do the calculation once.
69 # A number of the above factors, however, can be set after we've already
70 # been asked to return a string for a Node, because a Repository() or
71 # BuildDir() call or the like may not occur until later in SConscript
72 # files. So this variable controls whether we bother trying to save
73 # string values for Nodes. The wrapper interface can set this whenever
74 # they're done mucking with Repository and BuildDir and the other stuff,
75 # to let this module know it can start returning saved string values
80 def save_strings(val):
85 # SCons.Action objects for interacting with the outside world.
87 # The Node.FS methods in this module should use these actions to
88 # create and/or remove files and directories; they should *not* use
89 # os.{link,symlink,unlink,mkdir}(), etc., directly.
91 # Using these SCons.Action objects ensures that descriptions of these
92 # external activities are properly displayed, that the displays are
93 # suppressed when the -s (silent) option is used, and (most importantly)
94 # the actions are disabled when the the -n option is used, in which case
95 # there should be *no* changes to the external file system(s)...
98 if hasattr(os, 'link'):
99 def _hardlink_func(fs, src, dst):
100 # If the source is a symlink, we can't just hard-link to it
101 # because a relative symlink may point somewhere completely
102 # different. We must disambiguate the symlink and then
103 # hard-link the final destination file.
104 while fs.islink(src):
105 link = fs.readlink(src)
106 if not os.path.isabs(link):
109 src = os.path.join(os.path.dirname(src), link)
112 _hardlink_func = None
114 if hasattr(os, 'symlink'):
115 def _softlink_func(fs, src, dst):
118 _softlink_func = None
120 def _copy_func(fs, src, dest):
121 shutil.copy2(src, dest)
123 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
126 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
127 'hard-copy', 'soft-copy', 'copy']
129 Link_Funcs = [] # contains the callables of the specified duplication style
131 def set_duplicate(duplicate):
132 # Fill in the Link_Funcs list according to the argument
133 # (discarding those not available on the platform).
135 # Set up the dictionary that maps the argument names to the
136 # underlying implementations. We do this inside this function,
137 # not in the top-level module code, so that we can remap os.link
138 # and os.symlink for testing purposes.
140 'hard' : _hardlink_func,
141 'soft' : _softlink_func,
145 if not duplicate in Valid_Duplicates:
146 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
147 "should be in Valid_Duplicates")
150 for func in string.split(duplicate,'-'):
152 Link_Funcs.append(link_dict[func])
154 def LinkFunc(target, source, env):
155 # Relative paths cause problems with symbolic links, so
156 # we use absolute paths, which may be a problem for people
157 # who want to move their soft-linked src-trees around. Those
158 # people should use the 'hard-copy' mode, softlinks cannot be
159 # used for that; at least I have no idea how ...
160 src = source[0].abspath
161 dest = target[0].abspath
162 dir, file = os.path.split(dest)
163 if dir and not target[0].fs.isdir(dir):
166 # Set a default order of link functions.
167 set_duplicate('hard-soft-copy')
169 # Now link the files with the previously specified order.
170 for func in Link_Funcs:
174 except (IOError, OSError):
175 # An OSError indicates something happened like a permissions
176 # problem or an attempt to symlink across file-system
177 # boundaries. An IOError indicates something like the file
178 # not existing. In either case, keeping trying additional
179 # functions in the list and only raise an error if the last
181 if func == Link_Funcs[-1]:
182 # exception of the last link method (copy) are fatal
188 Link = SCons.Action.Action(LinkFunc, None)
189 def LocalString(target, source, env):
190 return 'Local copy of %s from %s' % (target[0], source[0])
192 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
194 def UnlinkFunc(target, source, env):
196 t.fs.unlink(t.abspath)
199 Unlink = SCons.Action.Action(UnlinkFunc, None)
201 def MkdirFunc(target, source, env):
204 t.fs.mkdir(t.abspath)
207 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
211 def get_MkdirBuilder():
213 if MkdirBuilder is None:
215 import SCons.Defaults
216 # "env" will get filled in by Executor.get_build_env()
217 # calling SCons.Defaults.DefaultEnvironment() when necessary.
218 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
222 target_scanner = SCons.Defaults.DirEntryScanner,
223 name = "MkdirBuilder")
231 DefaultSCCSBuilder = None
232 DefaultRCSBuilder = None
234 def get_DefaultSCCSBuilder():
235 global DefaultSCCSBuilder
236 if DefaultSCCSBuilder is None:
238 # "env" will get filled in by Executor.get_build_env()
239 # calling SCons.Defaults.DefaultEnvironment() when necessary.
240 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
241 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
243 name = "DefaultSCCSBuilder")
244 return DefaultSCCSBuilder
246 def get_DefaultRCSBuilder():
247 global DefaultRCSBuilder
248 if DefaultRCSBuilder is None:
250 # "env" will get filled in by Executor.get_build_env()
251 # calling SCons.Defaults.DefaultEnvironment() when necessary.
252 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
253 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
255 name = "DefaultRCSBuilder")
256 return DefaultRCSBuilder
258 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
259 _is_cygwin = sys.platform == "cygwin"
260 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
265 return string.upper(x)
270 def __init__(self, type, do, ignore):
276 self.__call__ = self.do
277 def set_ignore(self):
278 self.__call__ = self.ignore
280 if self.type in list:
285 def do_diskcheck_match(node, predicate, errorfmt):
288 # If calling the predicate() cached a None value from stat(),
289 # remove it so it doesn't interfere with later attempts to
290 # build this Node as we walk the DAG. (This isn't a great way
291 # to do this, we're reaching into an interface that doesn't
292 # really belong to us, but it's all about performance, so
293 # for now we'll just document the dependency...)
294 if node._memo['stat'] is None:
295 del node._memo['stat']
296 except (AttributeError, KeyError):
299 raise TypeError, errorfmt % node.abspath
301 def ignore_diskcheck_match(node, predicate, errorfmt):
304 def do_diskcheck_rcs(node, name):
306 rcs_dir = node.rcs_dir
307 except AttributeError:
308 if node.entry_exists_on_disk('RCS'):
309 rcs_dir = node.Dir('RCS')
312 node.rcs_dir = rcs_dir
314 return rcs_dir.entry_exists_on_disk(name+',v')
317 def ignore_diskcheck_rcs(node, name):
320 def do_diskcheck_sccs(node, name):
322 sccs_dir = node.sccs_dir
323 except AttributeError:
324 if node.entry_exists_on_disk('SCCS'):
325 sccs_dir = node.Dir('SCCS')
328 node.sccs_dir = sccs_dir
330 return sccs_dir.entry_exists_on_disk('s.'+name)
333 def ignore_diskcheck_sccs(node, name):
336 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
337 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
338 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
346 def set_diskcheck(list):
347 for dc in diskcheckers:
350 def diskcheck_types():
351 return map(lambda dc: dc.type, diskcheckers)
355 class EntryProxy(SCons.Util.Proxy):
356 def __get_abspath(self):
358 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
359 entry.name + "_abspath")
361 def __get_filebase(self):
362 name = self.get().name
363 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
366 def __get_suffix(self):
367 name = self.get().name
368 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
371 def __get_file(self):
372 name = self.get().name
373 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
375 def __get_base_path(self):
376 """Return the file's directory and file name, with the
379 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
380 entry.name + "_base")
382 def __get_posix_path(self):
383 """Return the path with / as the path separator,
384 regardless of platform."""
389 r = string.replace(entry.get_path(), os.sep, '/')
390 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
392 def __get_windows_path(self):
393 """Return the path with \ as the path separator,
394 regardless of platform."""
399 r = string.replace(entry.get_path(), os.sep, '\\')
400 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
402 def __get_srcnode(self):
403 return EntryProxy(self.get().srcnode())
405 def __get_srcdir(self):
406 """Returns the directory containing the source node linked to this
407 node via BuildDir(), or the directory of this node if not linked."""
408 return EntryProxy(self.get().srcnode().dir)
410 def __get_rsrcnode(self):
411 return EntryProxy(self.get().srcnode().rfile())
413 def __get_rsrcdir(self):
414 """Returns the directory containing the source node linked to this
415 node via BuildDir(), or the directory of this node if not linked."""
416 return EntryProxy(self.get().srcnode().rfile().dir)
419 return EntryProxy(self.get().dir)
421 dictSpecialAttrs = { "base" : __get_base_path,
422 "posix" : __get_posix_path,
423 "windows" : __get_windows_path,
424 "win32" : __get_windows_path,
425 "srcpath" : __get_srcnode,
426 "srcdir" : __get_srcdir,
428 "abspath" : __get_abspath,
429 "filebase" : __get_filebase,
430 "suffix" : __get_suffix,
432 "rsrcpath" : __get_rsrcnode,
433 "rsrcdir" : __get_rsrcdir,
436 def __getattr__(self, name):
437 # This is how we implement the "special" attributes
438 # such as base, posix, srcdir, etc.
440 attr_function = self.dictSpecialAttrs[name]
443 attr = SCons.Util.Proxy.__getattr__(self, name)
444 except AttributeError:
446 classname = string.split(str(entry.__class__), '.')[-1]
447 if classname[-2:] == "'>":
448 # new-style classes report their name as:
449 # "<class 'something'>"
450 # instead of the classic classes:
452 classname = classname[:-2]
453 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
456 return attr_function(self)
458 class Base(SCons.Node.Node):
459 """A generic class for file system entries. This class is for
460 when we don't know yet whether the entry being looked up is a file
461 or a directory. Instances of this class can morph into either
462 Dir or File objects by a later, more precise lookup.
464 Note: this class does not define __cmp__ and __hash__ for
465 efficiency reasons. SCons does a lot of comparing of
466 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
467 as fast as possible, which means we want to use Python's built-in
468 object identity comparisons.
471 memoizer_counters = []
473 def __init__(self, name, directory, fs):
474 """Initialize a generic Node.FS.Base object.
476 Call the superclass initialization, take care of setting up
477 our relative and absolute paths, identify our parent
478 directory, and indicate that this node should use
480 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
481 SCons.Node.Node.__init__(self)
484 self.suffix = SCons.Util.splitext(name)[1]
487 assert directory, "A directory must be provided"
489 self.abspath = directory.entry_abspath(name)
490 if directory.path == '.':
493 self.path = directory.entry_path(name)
494 if directory.tpath == '.':
497 self.tpath = directory.entry_tpath(name)
498 self.path_elements = directory.path_elements + [self]
501 self.cwd = None # will hold the SConscript directory for target nodes
502 self.duplicate = directory.duplicate
504 def must_be_same(self, klass):
506 This node, which already existed, is being looked up as the
507 specified klass. Raise an exception if it isn't.
509 if self.__class__ is klass or klass is Entry:
511 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
512 (self.__class__.__name__, self.path, klass.__name__)
517 def get_suffix(self):
524 """A Node.FS.Base object's string representation is its path
528 return self._save_str()
529 return self._get_str()
531 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
535 return self._memo['_save_str']
538 result = self._get_str()
539 self._memo['_save_str'] = result
543 if self.duplicate or self.is_derived():
544 return self.get_path()
545 return self.srcnode().get_path()
549 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
552 try: return self._memo['stat']
553 except KeyError: pass
554 try: result = self.fs.stat(self.abspath)
555 except os.error: result = None
556 self._memo['stat'] = result
560 return not self.stat() is None
563 return self.rfile().exists()
567 if st: return st[stat.ST_MTIME]
572 if st: return st[stat.ST_SIZE]
577 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
581 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
583 if hasattr(os, 'symlink'):
585 try: st = self.fs.lstat(self.abspath)
586 except os.error: return 0
587 return stat.S_ISLNK(st[stat.ST_MODE])
590 return 0 # no symlinks
592 def is_under(self, dir):
596 return self.dir.is_under(dir)
602 """If this node is in a build path, return the node
603 corresponding to its source file. Otherwise, return
610 srcnode = dir.srcdir.Entry(name)
611 srcnode.must_be_same(self.__class__)
613 name = dir.name + os.sep + name
617 def get_path(self, dir=None):
618 """Return path relative to the current working directory of the
619 Node.FS.Base object that owns us."""
621 dir = self.fs.getcwd()
624 path_elems = self.path_elements
625 try: i = path_elems.index(dir)
626 except ValueError: pass
627 else: path_elems = path_elems[i+1:]
628 path_elems = map(lambda n: n.name, path_elems)
629 return string.join(path_elems, os.sep)
631 def set_src_builder(self, builder):
632 """Set the source code builder for this node."""
633 self.sbuilder = builder
634 if not self.has_builder():
635 self.builder_set(builder)
637 def src_builder(self):
638 """Fetch the source code builder for this node.
640 If there isn't one, we cache the source code builder specified
641 for the directory (which in turn will cache the value from its
642 parent directory, and so on up to the file system root).
646 except AttributeError:
647 scb = self.dir.src_builder()
651 def get_abspath(self):
652 """Get the absolute path of the file."""
655 def for_signature(self):
656 # Return just our name. Even an absolute path would not work,
657 # because that can change thanks to symlinks or remapped network
661 def get_subst_proxy(self):
664 except AttributeError:
665 ret = EntryProxy(self)
669 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
672 Generates a target entry that corresponds to this entry (usually
673 a source file) with the specified prefix and suffix.
675 Note that this method can be overridden dynamically for generated
676 files that need different behavior. See Tool/swig.py for
679 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
681 def _Rfindalldirs_key(self, pathlist):
684 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
686 def Rfindalldirs(self, pathlist):
688 Return all of the directories for a given path list, including
689 corresponding "backing" directories in any repositories.
691 The Node lookups are relative to this Node (typically a
692 directory), so memoizing result saves cycles from looking
693 up the same path for each target in a given directory.
696 memo_dict = self._memo['Rfindalldirs']
699 self._memo['Rfindalldirs'] = memo_dict
702 return memo_dict[pathlist]
706 create_dir_relative_to_self = self.Dir
708 for path in pathlist:
709 if isinstance(path, SCons.Node.Node):
712 dir = create_dir_relative_to_self(path)
713 result.extend(dir.get_all_rdirs())
715 memo_dict[pathlist] = result
719 def RDirs(self, pathlist):
720 """Search for a list of directories in the Repository list."""
721 cwd = self.cwd or self.fs._cwd
722 return cwd.Rfindalldirs(pathlist)
724 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
728 return self._memo['rentry']
732 if not self.exists():
733 norm_name = _my_normcase(self.name)
734 for dir in self.dir.get_all_rdirs():
736 node = dir.entries[norm_name]
738 if dir.entry_exists_on_disk(self.name):
739 result = dir.Entry(self.name)
741 self._memo['rentry'] = result
745 """This is the class for generic Node.FS entries--that is, things
746 that could be a File or a Dir, but we're just not sure yet.
747 Consequently, the methods in this class really exist just to
748 transform their associated object into the right class when the
749 time comes, and then call the same-named method in the transformed
752 def diskcheck_match(self):
755 def disambiguate(self, must_exist=None):
762 self.__class__ = File
766 # There was nothing on-disk at this location, so look in
769 # We can't just use self.srcnode() straight away because
770 # that would create an actual Node for this file in the src
771 # directory, and there might not be one. Instead, use the
772 # dir_on_disk() method to see if there's something on-disk
773 # with that name, in which case we can go ahead and call
774 # self.srcnode() to create the right type of entry.
775 srcdir = self.dir.srcnode()
776 if srcdir != self.dir and \
777 srcdir.entry_exists_on_disk(self.name) and \
778 self.srcnode().isdir():
782 msg = "No such file or directory: '%s'" % self.abspath
783 raise SCons.Errors.UserError, msg
785 self.__class__ = File
791 """We're a generic Entry, but the caller is actually looking for
792 a File at this point, so morph into one."""
793 self.__class__ = File
796 return File.rfile(self)
798 def scanner_key(self):
799 return self.get_suffix()
801 def get_contents(self):
802 """Fetch the contents of the entry.
804 Since this should return the real contents from the file
805 system, we check to see into what sort of subclass we should
808 self = self.disambiguate(must_exist=1)
809 except SCons.Errors.UserError:
810 # There was nothing on disk with which to disambiguate
811 # this entry. Leave it as an Entry, but return a null
812 # string so calls to get_contents() in emitters and the
813 # like (e.g. in qt.py) don't have to disambiguate by hand
814 # or catch the exception.
817 return self.get_contents()
819 def must_be_same(self, klass):
820 """Called to make sure a Node is a Dir. Since we're an
821 Entry, we can morph into one."""
822 if not self.__class__ is klass:
823 self.__class__ = klass
827 # The following methods can get called before the Taskmaster has
828 # had a chance to call disambiguate() directly to see if this Entry
829 # should really be a Dir or a File. We therefore use these to call
830 # disambiguate() transparently (from our caller's point of view).
832 # Right now, this minimal set of methods has been derived by just
833 # looking at some of the methods that will obviously be called early
834 # in any of the various Taskmasters' calling sequences, and then
835 # empirically figuring out which additional methods are necessary
836 # to make various tests pass.
839 """Return if the Entry exists. Check the file system to see
840 what we should turn into first. Assume a file if there's no
842 return self.disambiguate().exists()
844 def rel_path(self, other):
845 d = self.disambiguate()
846 if d.__class__ == Entry:
847 raise "rel_path() could not disambiguate File/Dir"
848 return d.rel_path(other)
850 # This is for later so we can differentiate between Entry the class and Entry
851 # the method of the FS class.
857 if SCons.Memoize.use_memoizer:
858 __metaclass__ = SCons.Memoize.Memoized_Metaclass
860 # This class implements an abstraction layer for operations involving
861 # a local file system. Essentially, this wraps any function in
862 # the os, os.path or shutil modules that we use to actually go do
863 # anything with or to the local file system.
865 # Note that there's a very good chance we'll refactor this part of
866 # the architecture in some way as we really implement the interface(s)
867 # for remote file system Nodes. For example, the right architecture
868 # might be to have this be a subclass instead of a base class.
869 # Nevertheless, we're using this as a first step in that direction.
871 # We're not using chdir() yet because the calling subclass method
872 # needs to use os.chdir() directly to avoid recursion. Will we
873 # really need this one?
874 #def chdir(self, path):
875 # return os.chdir(path)
876 def chmod(self, path, mode):
877 return os.chmod(path, mode)
878 def copy2(self, src, dst):
879 return shutil.copy2(src, dst)
880 def exists(self, path):
881 return os.path.exists(path)
882 def getmtime(self, path):
883 return os.path.getmtime(path)
884 def getsize(self, path):
885 return os.path.getsize(path)
886 def isdir(self, path):
887 return os.path.isdir(path)
888 def isfile(self, path):
889 return os.path.isfile(path)
890 def link(self, src, dst):
891 return os.link(src, dst)
892 def lstat(self, path):
893 return os.lstat(path)
894 def listdir(self, path):
895 return os.listdir(path)
896 def makedirs(self, path):
897 return os.makedirs(path)
898 def mkdir(self, path):
899 return os.mkdir(path)
900 def rename(self, old, new):
901 return os.rename(old, new)
902 def stat(self, path):
904 def symlink(self, src, dst):
905 return os.symlink(src, dst)
906 def open(self, path):
908 def unlink(self, path):
909 return os.unlink(path)
911 if hasattr(os, 'symlink'):
912 def islink(self, path):
913 return os.path.islink(path)
915 def islink(self, path):
916 return 0 # no symlinks
918 if hasattr(os, 'readlink'):
919 def readlink(self, file):
920 return os.readlink(file)
922 def readlink(self, file):
927 # # Skeleton for the obvious methods we might need from the
928 # # abstraction layer for a remote filesystem.
929 # def upload(self, local_src, remote_dst):
931 # def download(self, remote_src, local_dst):
937 memoizer_counters = []
939 def __init__(self, path = None):
940 """Initialize the Node.FS subsystem.
942 The supplied path is the top of the source tree, where we
943 expect to find the top-level build file. If no path is
944 supplied, the current directory is the default.
946 The path argument must be a valid absolute path.
948 if __debug__: logInstanceCreation(self, 'Node.FS')
953 self.SConstruct_dir = None
954 self.max_drift = default_max_drift
958 self.pathTop = os.getcwd()
961 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
963 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
968 def set_SConstruct_dir(self, dir):
969 self.SConstruct_dir = dir
971 def get_max_drift(self):
972 return self.max_drift
974 def set_max_drift(self, max_drift):
975 self.max_drift = max_drift
980 def _doLookup_key(self, fsclass, name, directory = None, create = 1):
981 return (fsclass, name, directory)
983 memoizer_counters.append(SCons.Memoize.CountDict('_doLookup', _doLookup_key))
985 def _doLookup(self, fsclass, name, directory = None, create = 1):
986 """This method differs from the File and Dir factory methods in
987 one important way: the meaning of the directory parameter.
988 In this method, if directory is None or not supplied, the supplied
989 name is expected to be an absolute path. If you try to look up a
990 relative path with directory=None, then an AssertionError will be
993 memo_key = (fsclass, name, directory)
995 memo_dict = self._memo['_doLookup']
998 self._memo['_doLookup'] = memo_dict
1001 return memo_dict[memo_key]
1006 # This is a stupid hack to compensate for the fact that the
1007 # POSIX and Windows versions of os.path.normpath() behave
1008 # differently in older versions of Python. In particular,
1010 # os.path.normpath('./') == '.'
1012 # os.path.normpath('./') == ''
1013 # os.path.normpath('.\\') == ''
1015 # This is a definite bug in the Python library, but we have
1018 path_orig = string.split(name, os.sep)
1019 path_norm = string.split(_my_normcase(name), os.sep)
1021 first_orig = path_orig.pop(0) # strip first element
1022 unused = path_norm.pop(0) # strip first element
1024 drive, path_first = os.path.splitdrive(first_orig)
1026 path_orig = [ path_first, ] + path_orig
1027 path_norm = [ _my_normcase(path_first), ] + path_norm
1030 drive = _my_normcase(drive)
1032 directory = self.Root[drive]
1035 raise SCons.Errors.UserError
1036 directory = RootDir(drive, self)
1037 self.Root[drive] = directory
1039 self.Root[self.defaultDrive] = directory
1040 elif drive == self.defaultDrive:
1041 self.Root[''] = directory
1044 memo_dict[memo_key] = directory
1047 last_orig = path_orig.pop() # strip last element
1048 last_norm = path_norm.pop() # strip last element
1050 # Lookup the directory
1051 for orig, norm in map(None, path_orig, path_norm):
1053 entries = directory.entries
1054 except AttributeError:
1055 # We tried to look up the entry in either an Entry or
1056 # a File. Give whatever it is a chance to do what's
1057 # appropriate: morph into a Dir or raise an exception.
1058 directory.must_be_same(Dir)
1059 entries = directory.entries
1061 directory = entries[norm]
1064 raise SCons.Errors.UserError
1066 d = Dir(orig, directory, self)
1068 # Check the file system (or not, as configured) to make
1069 # sure there isn't already a file there.
1072 directory.entries[norm] = d
1073 directory.add_wkid(d)
1076 directory.must_be_same(Dir)
1079 e = directory.entries[last_norm]
1082 raise SCons.Errors.UserError
1084 result = fsclass(last_orig, directory, self)
1086 # Check the file system (or not, as configured) to make
1087 # sure there isn't already a directory at the path on
1088 # disk where we just created a File node, and vice versa.
1089 result.diskcheck_match()
1091 directory.entries[last_norm] = result
1092 directory.add_wkid(result)
1094 e.must_be_same(fsclass)
1097 memo_dict[memo_key] = result
1101 def _transformPath(self, name, directory):
1102 """Take care of setting up the correct top-level directory,
1103 usually in preparation for a call to doLookup().
1105 If the path name is prepended with a '#', then it is unconditionally
1106 interpreted as relative to the top-level directory of this FS.
1108 If directory is None, and name is a relative path,
1109 then the same applies.
1112 # Decide if this is a top-relative look up. The normal case
1113 # (by far) is handed a non-zero-length string to look up,
1114 # so just (try to) check for the initial '#'.
1115 top_relative = (name[0] == '#')
1116 except (AttributeError, IndexError):
1117 # The exceptions we may encounter in unusual cases:
1118 # AttributeError: a proxy without a __getitem__() method.
1119 # IndexError: a null string.
1120 top_relative = False
1123 directory = self.Top
1125 if name and (name[0] == os.sep or name[0] == '/'):
1126 # Correct such that '#/foo' is equivalent
1129 name = os.path.normpath(os.path.join('.', name))
1130 return (name, directory)
1132 directory = self._cwd
1133 return (os.path.normpath(name), directory)
1135 def chdir(self, dir, change_os_dir=0):
1136 """Change the current working directory for lookups.
1137 If change_os_dir is true, we will also change the "real" cwd
1145 os.chdir(dir.abspath)
1150 def Entry(self, name, directory = None, create = 1, klass=None):
1151 """Lookup or create a generic Entry node with the specified name.
1152 If the name is a relative path (begins with ./, ../, or a file
1153 name), then it is looked up relative to the supplied directory
1154 node, or to the top level directory of the FS (supplied at
1155 construction time) if no directory is supplied.
1161 if isinstance(name, Base):
1162 name.must_be_same(klass)
1165 if directory and not isinstance(directory, Dir):
1166 directory = self.Dir(directory)
1167 name, directory = self._transformPath(name, directory)
1168 return self._doLookup(klass, name, directory, create)
1170 def File(self, name, directory = None, create = 1):
1171 """Lookup or create a File node with the specified name. If
1172 the name is a relative path (begins with ./, ../, or a file name),
1173 then it is looked up relative to the supplied directory node,
1174 or to the top level directory of the FS (supplied at construction
1175 time) if no directory is supplied.
1177 This method will raise TypeError if a directory is found at the
1180 return self.Entry(name, directory, create, File)
1182 def Dir(self, name, directory = None, create = 1):
1183 """Lookup or create a Dir node with the specified name. If
1184 the name is a relative path (begins with ./, ../, or a file name),
1185 then it is looked up relative to the supplied directory node,
1186 or to the top level directory of the FS (supplied at construction
1187 time) if no directory is supplied.
1189 This method will raise TypeError if a normal file is found at the
1192 return self.Entry(name, directory, create, Dir)
1194 def BuildDir(self, build_dir, src_dir, duplicate=1):
1195 """Link the supplied build directory to the source directory
1196 for purposes of building files."""
1198 if not isinstance(src_dir, SCons.Node.Node):
1199 src_dir = self.Dir(src_dir)
1200 if not isinstance(build_dir, SCons.Node.Node):
1201 build_dir = self.Dir(build_dir)
1202 if src_dir.is_under(build_dir):
1203 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1204 if build_dir.srcdir:
1205 if build_dir.srcdir == src_dir:
1206 return # We already did this.
1207 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1208 build_dir.link(src_dir, duplicate)
1210 def Repository(self, *dirs):
1211 """Specify Repository directories to search."""
1213 if not isinstance(d, SCons.Node.Node):
1215 self.Top.addRepository(d)
1217 def build_dir_target_climb(self, orig, dir, tail):
1218 """Create targets in corresponding build directories
1220 Climb the directory tree, and look up path names
1221 relative to any linked build directories we find.
1223 Even though this loops and walks up the tree, we don't memoize
1224 the return value because this is really only used to process
1225 the command-line targets.
1229 fmt = "building associated BuildDir targets: %s"
1232 for bd in dir.build_dirs:
1233 if start_dir.is_under(bd):
1234 # If already in the build-dir location, don't reflect
1235 return [orig], fmt % str(orig)
1236 p = apply(os.path.join, [bd.path] + tail)
1237 targets.append(self.Entry(p))
1238 tail = [dir.name] + tail
1241 message = fmt % string.join(map(str, targets))
1242 return targets, message
1244 class DirNodeInfo(SCons.Node.NodeInfoBase):
1247 class DirBuildInfo(SCons.Node.BuildInfoBase):
1251 """A class for directories in a file system.
1254 memoizer_counters = []
1256 NodeInfo = DirNodeInfo
1257 BuildInfo = DirBuildInfo
1259 def __init__(self, name, directory, fs):
1260 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1261 Base.__init__(self, name, directory, fs)
1265 """Turn a file system Node (either a freshly initialized directory
1266 object or a separate Entry object) into a proper directory object.
1268 Set up this directory's entries and hook it into the file
1269 system tree. Specify that directories (this Node) don't use
1270 signatures for calculating whether they're current.
1273 self.repositories = []
1277 self.entries['.'] = self
1278 self.entries['..'] = self.dir
1281 self._sconsign = None
1282 self.build_dirs = []
1284 # Don't just reset the executor, replace its action list,
1285 # because it might have some pre-or post-actions that need to
1287 self.builder = get_MkdirBuilder()
1288 self.get_executor().set_action_list(self.builder.action)
1290 def diskcheck_match(self):
1291 diskcheck_match(self, self.isfile,
1292 "File %s found where directory expected.")
1294 def __clearRepositoryCache(self, duplicate=None):
1295 """Called when we change the repository(ies) for a directory.
1296 This clears any cached information that is invalidated by changing
1299 for node in self.entries.values():
1300 if node != self.dir:
1301 if node != self and isinstance(node, Dir):
1302 node.__clearRepositoryCache(duplicate)
1307 except AttributeError:
1309 if duplicate != None:
1310 node.duplicate=duplicate
1312 def __resetDuplicate(self, node):
1314 node.duplicate = node.get_dir().duplicate
1316 def Entry(self, name):
1317 """Create an entry node named 'name' relative to this directory."""
1318 return self.fs.Entry(name, self)
1320 def Dir(self, name):
1321 """Create a directory node named 'name' relative to this directory."""
1322 dir = self.fs.Dir(name, self)
1325 def File(self, name):
1326 """Create a file node named 'name' relative to this directory."""
1327 return self.fs.File(name, self)
1329 def link(self, srcdir, duplicate):
1330 """Set this directory as the build directory for the
1331 supplied source directory."""
1332 self.srcdir = srcdir
1333 self.duplicate = duplicate
1334 self.__clearRepositoryCache(duplicate)
1335 srcdir.build_dirs.append(self)
1337 def getRepositories(self):
1338 """Returns a list of repositories for this directory.
1340 if self.srcdir and not self.duplicate:
1341 return self.srcdir.get_all_rdirs() + self.repositories
1342 return self.repositories
1344 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1346 def get_all_rdirs(self):
1348 return self._memo['get_all_rdirs']
1356 for rep in dir.getRepositories():
1357 result.append(rep.Dir(fname))
1358 fname = dir.name + os.sep + fname
1361 self._memo['get_all_rdirs'] = result
1365 def addRepository(self, dir):
1366 if dir != self and not dir in self.repositories:
1367 self.repositories.append(dir)
1369 self.__clearRepositoryCache()
1372 return self.entries['..']
1374 def _rel_path_key(self, other):
1377 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1379 def rel_path(self, other):
1380 """Return a path to "other" relative to this directory.
1383 memo_dict = self._memo['rel_path']
1386 self._memo['rel_path'] = memo_dict
1389 return memo_dict[other]
1397 elif not other in self.path_elements:
1400 other_dir = other.get_dir()
1401 except AttributeError:
1404 if other_dir is None:
1407 dir_rel_path = self.rel_path(other_dir)
1408 if dir_rel_path == '.':
1411 result = dir_rel_path + os.sep + other.name
1415 i = self.path_elements.index(other) + 1
1417 path_elems = ['..'] * (len(self.path_elements) - i) \
1418 + map(lambda n: n.name, other.path_elements[i:])
1420 result = string.join(path_elems, os.sep)
1422 memo_dict[other] = result
1426 def get_env_scanner(self, env, kw={}):
1427 import SCons.Defaults
1428 return SCons.Defaults.DirEntryScanner
1430 def get_target_scanner(self):
1431 import SCons.Defaults
1432 return SCons.Defaults.DirEntryScanner
1434 def get_found_includes(self, env, scanner, path):
1435 """Return this directory's implicit dependencies.
1437 We don't bother caching the results because the scan typically
1438 shouldn't be requested more than once (as opposed to scanning
1439 .h file contents, which can be requested as many times as the
1440 files is #included by other files).
1444 # Clear cached info for this Dir. If we already visited this
1445 # directory on our walk down the tree (because we didn't know at
1446 # that point it was being used as the source for another Node)
1447 # then we may have calculated build signature before realizing
1448 # we had to scan the disk. Now that we have to, though, we need
1449 # to invalidate the old calculated signature so that any node
1450 # dependent on our directory structure gets one that includes
1451 # info about everything on disk.
1453 return scanner(self, env, path)
1455 def build(self, **kw):
1456 """A null "builder" for directories."""
1458 if not self.builder is MkdirBuilder:
1459 apply(SCons.Node.Node.build, [self,], kw)
1462 """Create this directory, silently and without worrying about
1463 whether the builder is the default or not."""
1469 listDirs.append(parent)
1472 raise SCons.Errors.StopError, parent.path
1475 for dirnode in listDirs:
1477 # Don't call dirnode.build(), call the base Node method
1478 # directly because we definitely *must* create this
1479 # directory. The dirnode.build() method will suppress
1480 # the build if it's the default builder.
1481 SCons.Node.Node.build(dirnode)
1482 dirnode.get_executor().nullify()
1483 # The build() action may or may not have actually
1484 # created the directory, depending on whether the -n
1485 # option was used or not. Delete the _exists and
1486 # _rexists attributes so they can be reevaluated.
1491 def multiple_side_effect_has_builder(self):
1493 return not self.builder is MkdirBuilder and self.has_builder()
1495 def alter_targets(self):
1496 """Return any corresponding targets in a build directory.
1498 return self.fs.build_dir_target_climb(self, self, [])
1500 def scanner_key(self):
1501 """A directory does not get scanned."""
1504 def get_contents(self):
1505 """Return aggregate contents of all our children."""
1506 contents = map(lambda n: n.get_contents(), self.children())
1507 return string.join(contents, '')
1512 def do_duplicate(self, src):
1515 def current(self, calc=None):
1516 """If any child is not up-to-date, then this directory isn't,
1518 if not self.builder is MkdirBuilder and not self.exists():
1520 up_to_date = SCons.Node.up_to_date
1521 for kid in self.children():
1522 if kid.get_state() > up_to_date:
1527 if not self.exists():
1528 norm_name = _my_normcase(self.name)
1529 for dir in self.dir.get_all_rdirs():
1530 try: node = dir.entries[norm_name]
1531 except KeyError: node = dir.dir_on_disk(self.name)
1532 if node and node.exists() and \
1533 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1538 """Return the .sconsign file info for this directory,
1539 creating it first if necessary."""
1540 if not self._sconsign:
1541 import SCons.SConsign
1542 self._sconsign = SCons.SConsign.ForDirectory(self)
1543 return self._sconsign
1546 """Dir has a special need for srcnode()...if we
1547 have a srcdir attribute set, then that *is* our srcnode."""
1550 return Base.srcnode(self)
1552 def get_timestamp(self):
1553 """Return the latest timestamp from among our children"""
1555 for kid in self.children():
1556 if kid.get_timestamp() > stamp:
1557 stamp = kid.get_timestamp()
1560 def entry_abspath(self, name):
1561 return self.abspath + os.sep + name
1563 def entry_path(self, name):
1564 return self.path + os.sep + name
1566 def entry_tpath(self, name):
1567 return self.tpath + os.sep + name
1569 def entry_exists_on_disk(self, name):
1571 d = self.on_disk_entries
1572 except AttributeError:
1575 entries = os.listdir(self.abspath)
1579 for entry in map(_my_normcase, entries):
1581 self.on_disk_entries = d
1582 return d.has_key(_my_normcase(name))
1584 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1586 def srcdir_list(self):
1588 return self._memo['srcdir_list']
1598 d = dir.srcdir.Dir(dirname)
1600 # Shouldn't source from something in the build path:
1601 # build_dir is probably under src_dir, in which case
1602 # we are reflecting.
1605 dirname = dir.name + os.sep + dirname
1608 self._memo['srcdir_list'] = result
1612 def srcdir_duplicate(self, name):
1613 for dir in self.srcdir_list():
1614 if dir.entry_exists_on_disk(name):
1615 srcnode = dir.Entry(name).disambiguate()
1617 node = self.Entry(name).disambiguate()
1618 node.do_duplicate(srcnode)
1624 def _srcdir_find_file_key(self, filename):
1627 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1629 def srcdir_find_file(self, filename):
1631 memo_dict = self._memo['srcdir_find_file']
1634 self._memo['srcdir_find_file'] = memo_dict
1637 return memo_dict[filename]
1642 if (isinstance(node, File) or isinstance(node, Entry)) and \
1643 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1647 norm_name = _my_normcase(filename)
1649 for rdir in self.get_all_rdirs():
1650 try: node = rdir.entries[norm_name]
1651 except KeyError: node = rdir.file_on_disk(filename)
1652 else: node = func(node)
1654 result = (node, self)
1655 memo_dict[filename] = result
1658 for srcdir in self.srcdir_list():
1659 for rdir in srcdir.get_all_rdirs():
1660 try: node = rdir.entries[norm_name]
1661 except KeyError: node = rdir.file_on_disk(filename)
1662 else: node = func(node)
1664 result = (File(filename, self, self.fs), srcdir)
1665 memo_dict[filename] = result
1668 result = (None, None)
1669 memo_dict[filename] = result
1672 def dir_on_disk(self, name):
1673 if self.entry_exists_on_disk(name):
1674 try: return self.Dir(name)
1675 except TypeError: pass
1678 def file_on_disk(self, name):
1679 if self.entry_exists_on_disk(name) or \
1680 diskcheck_rcs(self, name) or \
1681 diskcheck_sccs(self, name):
1682 try: return self.File(name)
1683 except TypeError: pass
1684 node = self.srcdir_duplicate(name)
1685 if isinstance(node, Dir):
1689 def walk(self, func, arg):
1691 Walk this directory tree by calling the specified function
1692 for each directory in the tree.
1694 This behaves like the os.path.walk() function, but for in-memory
1695 Node.FS.Dir objects. The function takes the same arguments as
1696 the functions passed to os.path.walk():
1698 func(arg, dirname, fnames)
1700 Except that "dirname" will actually be the directory *Node*,
1701 not the string. The '.' and '..' entries are excluded from
1702 fnames. The fnames list may be modified in-place to filter the
1703 subdirectories visited or otherwise impose a specific order.
1704 The "arg" argument is always passed to func() and may be used
1705 in any way (or ignored, passing None is common).
1707 entries = self.entries
1708 names = entries.keys()
1711 func(arg, self, names)
1712 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1713 for dirname in filter(select_dirs, names):
1714 entries[dirname].walk(func, arg)
1717 """A class for the root directory of a file system.
1719 This is the same as a Dir class, except that the path separator
1720 ('/' or '\\') is actually part of the name, so we don't need to
1721 add a separator when creating the path names of entries within
1724 def __init__(self, name, fs):
1725 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1726 # We're going to be our own parent directory (".." entry and .dir
1727 # attribute) so we have to set up some values so Base.__init__()
1728 # won't gag won't it calls some of our methods.
1732 self.path_elements = []
1734 Base.__init__(self, name, self, fs)
1736 # Now set our paths to what we really want them to be: the
1737 # initial drive letter (the name) plus the directory separator.
1738 self.abspath = name + os.sep
1739 self.path = name + os.sep
1740 self.tpath = name + os.sep
1743 def must_be_same(self, klass):
1746 Base.must_be_same(self, klass)
1751 def entry_abspath(self, name):
1752 return self.abspath + name
1754 def entry_path(self, name):
1755 return self.path + name
1757 def entry_tpath(self, name):
1758 return self.tpath + name
1760 def is_under(self, dir):
1772 def src_builder(self):
1775 class FileNodeInfo(SCons.Node.NodeInfoBase):
1776 def __init__(self, node):
1777 SCons.Node.NodeInfoBase.__init__(self, node)
1779 def __cmp__(self, other):
1780 try: return cmp(self.bsig, other.bsig)
1781 except AttributeError: return 1
1782 def update(self, node):
1783 self.timestamp = node.get_timestamp()
1784 self.size = node.getsize()
1786 class FileBuildInfo(SCons.Node.BuildInfoBase):
1787 def __init__(self, node):
1788 SCons.Node.BuildInfoBase.__init__(self, node)
1790 def convert_to_sconsign(self):
1791 """Convert this FileBuildInfo object for writing to a .sconsign file
1793 We hung onto the node that we refer to so that we can translate
1794 the lists of bsources, bdepends and bimplicit Nodes into strings
1795 relative to the node, but we don't want to write out that Node
1796 itself to the .sconsign file, so we delete the attribute in
1799 rel_path = self.node.rel_path
1800 delattr(self, 'node')
1801 for attr in ['bsources', 'bdepends', 'bimplicit']:
1803 val = getattr(self, attr)
1804 except AttributeError:
1807 setattr(self, attr, map(rel_path, val))
1808 def convert_from_sconsign(self, dir, name):
1809 """Convert a newly-read FileBuildInfo object for in-SCons use
1811 An on-disk BuildInfo comes without a reference to the node for
1812 which it's intended, so we have to convert the arguments and add
1813 back a self.node attribute. We don't worry here about converting
1814 the bsources, bdepends and bimplicit lists from strings to Nodes
1815 because they're not used in the normal case of just deciding
1816 whether or not to rebuild things.
1818 self.node = dir.Entry(name)
1819 def prepare_dependencies(self):
1820 """Prepare a FileBuildInfo object for explaining what changed
1822 The bsources, bdepends and bimplicit lists have all been stored
1823 on disk as paths relative to the Node for which they're stored
1824 as dependency info. Convert the strings to actual Nodes (for
1825 use by the --debug=explain code and --implicit-cache).
1827 def str_to_node(s, entry=self.node.dir.Entry):
1828 # This is a little bogus; we're going to mimic the lookup
1829 # order of env.arg2nodes() by hard-coding an Alias lookup
1830 # before we assume it's an Entry. This should be able to
1831 # go away once the Big Signature Refactoring pickles the
1832 # actual NodeInfo object, which will let us know precisely
1833 # what type of Node to turn it into.
1834 import SCons.Node.Alias
1835 n = SCons.Node.Alias.default_ans.lookup(s)
1839 for attr in ['bsources', 'bdepends', 'bimplicit']:
1841 val = getattr(self, attr)
1842 except AttributeError:
1845 setattr(self, attr, map(str_to_node, val))
1847 result = [ self.ninfo.format() ]
1848 bkids = self.bsources + self.bdepends + self.bimplicit
1849 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
1850 for i in xrange(len(bkids)):
1851 result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
1852 return string.join(result, '\n')
1854 class NodeInfo(FileNodeInfo):
1857 class BuildInfo(FileBuildInfo):
1861 """A class for files in a file system.
1864 memoizer_counters = []
1866 NodeInfo = FileNodeInfo
1867 BuildInfo = FileBuildInfo
1869 def diskcheck_match(self):
1870 diskcheck_match(self, self.isdir,
1871 "Directory %s found where file expected.")
1873 def __init__(self, name, directory, fs):
1874 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1875 Base.__init__(self, name, directory, fs)
1878 def Entry(self, name):
1879 """Create an entry node named 'name' relative to
1880 the SConscript directory of this file."""
1881 return self.cwd.Entry(name)
1883 def Dir(self, name):
1884 """Create a directory node named 'name' relative to
1885 the SConscript directory of this file."""
1886 return self.cwd.Dir(name)
1888 def Dirs(self, pathlist):
1889 """Create a list of directories relative to the SConscript
1890 directory of this file."""
1891 return map(lambda p, s=self: s.Dir(p), pathlist)
1893 def File(self, name):
1894 """Create a file node named 'name' relative to
1895 the SConscript directory of this file."""
1896 return self.cwd.File(name)
1898 #def generate_build_dict(self):
1899 # """Return an appropriate dictionary of values for building
1901 # return {'Dir' : self.Dir,
1902 # 'File' : self.File,
1903 # 'RDirs' : self.RDirs}
1906 """Turn a file system node into a File object."""
1907 self.scanner_paths = {}
1908 if not hasattr(self, '_local'):
1911 def scanner_key(self):
1912 return self.get_suffix()
1914 def get_contents(self):
1915 if not self.rexists():
1917 fname = self.rfile().abspath
1919 r = open(fname, "rb").read()
1920 except EnvironmentError, e:
1926 def get_timestamp(self):
1928 return self.rfile().getmtime()
1932 def store_info(self, obj):
1933 # Merge our build information into the already-stored entry.
1934 # This accomodates "chained builds" where a file that's a target
1935 # in one build (SConstruct file) is a source in a different build.
1936 # See test/chained-build.py for the use case.
1937 entry = self.get_stored_info()
1939 self.dir.sconsign().set_entry(self.name, entry)
1941 def get_stored_info(self):
1943 stored = self.dir.sconsign().get_entry(self.name)
1944 except (KeyError, OSError):
1945 return self.new_binfo()
1947 if not hasattr(stored, 'ninfo'):
1948 # Transition: The .sconsign file entry has no NodeInfo
1949 # object, which means it's a slightly older BuildInfo.
1950 # Copy over the relevant attributes.
1951 ninfo = stored.ninfo = self.new_ninfo()
1952 for attr in ninfo.__dict__.keys():
1954 setattr(ninfo, attr, getattr(stored, attr))
1955 except AttributeError:
1959 def get_stored_implicit(self):
1960 binfo = self.get_stored_info()
1961 binfo.prepare_dependencies()
1962 try: return binfo.bimplicit
1963 except AttributeError: return None
1965 def rel_path(self, other):
1966 return self.dir.rel_path(other)
1968 def _get_found_includes_key(self, env, scanner, path):
1969 return (id(env), id(scanner), path)
1971 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
1973 def get_found_includes(self, env, scanner, path):
1974 """Return the included implicit dependencies in this file.
1975 Cache results so we only scan the file once per path
1976 regardless of how many times this information is requested.
1978 memo_key = (id(env), id(scanner), path)
1980 memo_dict = self._memo['get_found_includes']
1983 self._memo['get_found_includes'] = memo_dict
1986 return memo_dict[memo_key]
1991 result = scanner(self, env, path)
1992 result = map(lambda N: N.disambiguate(), result)
1996 memo_dict[memo_key] = result
2000 def _createDir(self):
2001 # ensure that the directories for this node are
2005 def retrieve_from_cache(self):
2006 """Try to retrieve the node's content from a cache
2008 This method is called from multiple threads in a parallel build,
2009 so only do thread safe stuff here. Do thread unsafe stuff in
2012 Returns true iff the node was successfully retrieved.
2016 if not self.is_derived():
2018 return self.get_build_env().get_CacheDir().retrieve(self)
2022 Called just after this node is successfully built.
2024 # Push this file out to cache before the superclass Node.built()
2025 # method has a chance to clear the build signature, which it
2026 # will do if this file has a source scanner.
2028 # We have to clear the memoized values *before* we push it to
2029 # cache so that the memoization of the self.exists() return
2030 # value doesn't interfere.
2031 self.clear_memoized_values()
2033 self.get_build_env().get_CacheDir().push(self)
2034 SCons.Node.Node.built(self)
2038 self.get_build_env().get_CacheDir().push_if_forced(self)
2040 def has_src_builder(self):
2041 """Return whether this Node has a source builder or not.
2043 If this Node doesn't have an explicit source code builder, this
2044 is where we figure out, on the fly, if there's a transparent
2045 source code builder for it.
2047 Note that if we found a source builder, we also set the
2048 self.builder attribute, so that all of the methods that actually
2049 *build* this file don't have to do anything different.
2053 except AttributeError:
2057 scb = self.dir.src_builder()
2059 if diskcheck_sccs(self.dir, self.name):
2060 scb = get_DefaultSCCSBuilder()
2061 elif diskcheck_rcs(self.dir, self.name):
2062 scb = get_DefaultRCSBuilder()
2066 self.builder_set(scb)
2068 return not scb is None
2070 def alter_targets(self):
2071 """Return any corresponding targets in a build directory.
2073 if self.is_derived():
2075 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
2077 def is_pseudo_derived(self):
2078 return self.has_src_builder()
2080 def _rmv_existing(self):
2081 self.clear_memoized_values()
2082 Unlink(self, [], None)
2085 """Prepare for this file to be created."""
2086 SCons.Node.Node.prepare(self)
2088 if self.get_state() != SCons.Node.up_to_date:
2090 if self.is_derived() and not self.precious:
2091 self._rmv_existing()
2095 except SCons.Errors.StopError, drive:
2096 desc = "No drive `%s' for target `%s'." % (drive, self)
2097 raise SCons.Errors.StopError, desc
2100 """Remove this file."""
2101 if self.exists() or self.islink():
2102 self.fs.unlink(self.path)
2106 def do_duplicate(self, src):
2109 Unlink(self, None, None)
2110 except SCons.Errors.BuildError:
2113 Link(self, src, None)
2114 except SCons.Errors.BuildError, e:
2115 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2116 raise SCons.Errors.StopError, desc
2118 # The Link() action may or may not have actually
2119 # created the file, depending on whether the -n
2120 # option was used or not. Delete the _exists and
2121 # _rexists attributes so they can be reevaluated.
2124 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2128 return self._memo['exists']
2131 # Duplicate from source path if we are set up to do this.
2132 if self.duplicate and not self.is_derived() and not self.linked:
2133 src = self.srcnode()
2135 # At this point, src is meant to be copied in a build directory.
2137 if src.abspath != self.abspath:
2139 self.do_duplicate(src)
2140 # Can't return 1 here because the duplication might
2141 # not actually occur if the -n option is being used.
2143 # The source file does not exist. Make sure no old
2144 # copy remains in the build directory.
2145 if Base.exists(self) or self.islink():
2146 self.fs.unlink(self.path)
2147 # Return None explicitly because the Base.exists() call
2148 # above will have cached its value if the file existed.
2149 self._memo['exists'] = None
2151 result = Base.exists(self)
2152 self._memo['exists'] = result
2156 # SIGNATURE SUBSYSTEM
2159 def get_csig(self, calc=None):
2161 Generate a node's content signature, the digested signature
2165 cache - alternate node to use for the signature cache
2166 returns - the content signature
2169 return self.binfo.ninfo.csig
2170 except AttributeError:
2174 calc = self.calculator()
2176 max_drift = self.fs.max_drift
2177 mtime = self.get_timestamp()
2178 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
2182 old = self.get_stored_info().ninfo
2184 if old.timestamp and old.csig and old.timestamp == mtime:
2186 except AttributeError:
2189 csig = calc.module.signature(self)
2191 binfo = self.get_binfo()
2197 self.store_info(binfo)
2205 def is_up_to_date(self, node=None, bi=None):
2206 """Returns if the node is up-to-date with respect to stored
2207 BuildInfo. The default is to compare it against our own
2208 previously stored BuildInfo, but the stored BuildInfo from another
2209 Node (typically one in a Repository) can be used instead."""
2213 bi = node.get_stored_info()
2214 new = self.get_binfo()
2217 def current(self, calc=None):
2218 self.binfo = self.gen_binfo(calc)
2221 if not self.exists():
2222 # The file doesn't exist locally...
2225 # ...but there is one in a Repository...
2226 if self.is_up_to_date(r):
2227 # ...and it's even up-to-date...
2229 # ...and they'd like a local copy.
2230 LocalCopy(self, r, None)
2231 self.store_info(self.get_binfo())
2235 return self.is_up_to_date()
2237 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2241 return self._memo['rfile']
2245 if not self.exists():
2246 norm_name = _my_normcase(self.name)
2247 for dir in self.dir.get_all_rdirs():
2248 try: node = dir.entries[norm_name]
2249 except KeyError: node = dir.file_on_disk(self.name)
2250 if node and node.exists() and \
2251 (isinstance(node, File) or isinstance(node, Entry) \
2252 or not node.is_derived()):
2255 self._memo['rfile'] = result
2259 return str(self.rfile())
2261 def get_cachedir_bsig(self):
2262 import SCons.Sig.MD5
2263 ninfo = self.get_binfo().ninfo
2264 if not hasattr(ninfo, 'bsig'):
2266 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
2267 elif ninfo.bsig is None:
2269 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
2270 # Add the path to the cache signature, because multiple
2271 # targets built by the same action will all have the same
2272 # build signature, and we have to differentiate them somehow.
2273 return SCons.Sig.MD5.collect([ninfo.bsig, self.path])
2277 def get_default_fs():
2286 if SCons.Memoize.use_memoizer:
2287 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2289 memoizer_counters = []
2294 def _find_file_key(self, filename, paths, verbose=None):
2295 return (filename, paths)
2297 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2299 def find_file(self, filename, paths, verbose=None):
2301 find_file(str, [Dir()]) -> [nodes]
2303 filename - a filename to find
2304 paths - a list of directory path *nodes* to search in. Can be
2305 represented as a list, a tuple, or a callable that is
2306 called with no arguments and returns the list or tuple.
2308 returns - the node created from the found file.
2310 Find a node corresponding to either a derived file or a file
2311 that exists already.
2313 Only the first file found is returned, and none is returned
2314 if no file is found.
2316 memo_key = self._find_file_key(filename, paths)
2318 memo_dict = self._memo['find_file']
2321 self._memo['find_file'] = memo_dict
2324 return memo_dict[memo_key]
2329 if not SCons.Util.is_String(verbose):
2330 verbose = "find_file"
2331 if not callable(verbose):
2332 verbose = ' %s: ' % verbose
2333 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2335 verbose = lambda x: x
2337 filedir, filename = os.path.split(filename)
2339 def filedir_lookup(p, fd=filedir):
2343 # We tried to look up a Dir, but it seems there's
2344 # already a File (or something else) there. No big.
2346 paths = filter(None, map(filedir_lookup, paths))
2350 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2351 node, d = dir.srcdir_find_file(filename)
2353 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2357 memo_dict[memo_key] = result
2361 find_file = FileFinder().find_file