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
55 # The max_drift value: by default, use a cached signature value for
56 # any file that's been untouched for more than two days.
57 default_max_drift = 2*24*60*60
60 # We stringify these file system Nodes a lot. Turning a file system Node
61 # into a string is non-trivial, because the final string representation
62 # can depend on a lot of factors: whether it's a derived target or not,
63 # whether it's linked to a repository or source directory, and whether
64 # there's duplication going on. The normal technique for optimizing
65 # calculations like this is to memoize (cache) the string value, so you
66 # only have to do the calculation once.
68 # A number of the above factors, however, can be set after we've already
69 # been asked to return a string for a Node, because a Repository() or
70 # BuildDir() call or the like may not occur until later in SConscript
71 # files. So this variable controls whether we bother trying to save
72 # string values for Nodes. The wrapper interface can set this whenever
73 # they're done mucking with Repository and BuildDir and the other stuff,
74 # to let this module know it can start returning saved string values
79 def save_strings(val):
84 # SCons.Action objects for interacting with the outside world.
86 # The Node.FS methods in this module should use these actions to
87 # create and/or remove files and directories; they should *not* use
88 # os.{link,symlink,unlink,mkdir}(), etc., directly.
90 # Using these SCons.Action objects ensures that descriptions of these
91 # external activities are properly displayed, that the displays are
92 # suppressed when the -s (silent) option is used, and (most importantly)
93 # the actions are disabled when the the -n option is used, in which case
94 # there should be *no* changes to the external file system(s)...
97 if hasattr(os, 'link'):
98 def _hardlink_func(fs, src, dst):
99 # If the source is a symlink, we can't just hard-link to it
100 # because a relative symlink may point somewhere completely
101 # different. We must disambiguate the symlink and then
102 # hard-link the final destination file.
103 while fs.islink(src):
104 link = fs.readlink(src)
105 if not os.path.isabs(link):
108 src = os.path.join(os.path.dirname(src), link)
111 _hardlink_func = None
113 if hasattr(os, 'symlink'):
114 def _softlink_func(fs, src, dst):
117 _softlink_func = None
119 def _copy_func(fs, src, dest):
120 shutil.copy2(src, dest)
122 fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
125 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
126 'hard-copy', 'soft-copy', 'copy']
128 Link_Funcs = [] # contains the callables of the specified duplication style
130 def set_duplicate(duplicate):
131 # Fill in the Link_Funcs list according to the argument
132 # (discarding those not available on the platform).
134 # Set up the dictionary that maps the argument names to the
135 # underlying implementations. We do this inside this function,
136 # not in the top-level module code, so that we can remap os.link
137 # and os.symlink for testing purposes.
139 'hard' : _hardlink_func,
140 'soft' : _softlink_func,
144 if not duplicate in Valid_Duplicates:
145 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
146 "should be in Valid_Duplicates")
149 for func in string.split(duplicate,'-'):
151 Link_Funcs.append(link_dict[func])
153 def LinkFunc(target, source, env):
154 # Relative paths cause problems with symbolic links, so
155 # we use absolute paths, which may be a problem for people
156 # who want to move their soft-linked src-trees around. Those
157 # people should use the 'hard-copy' mode, softlinks cannot be
158 # used for that; at least I have no idea how ...
159 src = source[0].abspath
160 dest = target[0].abspath
161 dir, file = os.path.split(dest)
162 if dir and not target[0].fs.isdir(dir):
165 # Set a default order of link functions.
166 set_duplicate('hard-soft-copy')
168 # Now link the files with the previously specified order.
169 for func in Link_Funcs:
173 except (IOError, OSError):
174 # An OSError indicates something happened like a permissions
175 # problem or an attempt to symlink across file-system
176 # boundaries. An IOError indicates something like the file
177 # not existing. In either case, keeping trying additional
178 # functions in the list and only raise an error if the last
180 if func == Link_Funcs[-1]:
181 # exception of the last link method (copy) are fatal
187 Link = SCons.Action.Action(LinkFunc, None)
188 def LocalString(target, source, env):
189 return 'Local copy of %s from %s' % (target[0], source[0])
191 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
193 def UnlinkFunc(target, source, env):
195 t.fs.unlink(t.abspath)
198 Unlink = SCons.Action.Action(UnlinkFunc, None)
200 def MkdirFunc(target, source, env):
203 t.fs.mkdir(t.abspath)
206 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
210 def get_MkdirBuilder():
212 if MkdirBuilder is None:
214 import SCons.Defaults
215 # "env" will get filled in by Executor.get_build_env()
216 # calling SCons.Defaults.DefaultEnvironment() when necessary.
217 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
221 target_scanner = SCons.Defaults.DirEntryScanner,
222 name = "MkdirBuilder")
225 def CacheRetrieveFunc(target, source, env):
228 cachedir, cachefile = t.cachepath()
229 if not fs.exists(cachefile):
230 fs.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile)
232 fs.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
233 if SCons.Action.execute_actions:
234 fs.copy2(cachefile, t.path)
235 st = fs.stat(cachefile)
236 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
239 def CacheRetrieveString(target, source, env):
241 cachedir, cachefile = t.cachepath()
242 if t.fs.exists(cachefile):
243 return "Retrieved `%s' from cache" % t.path
246 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
248 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
250 def CachePushFunc(target, source, env):
253 cachedir, cachefile = t.cachepath()
254 if fs.exists(cachefile):
255 # Don't bother copying it if it's already there. Note that
256 # usually this "shouldn't happen" because if the file already
257 # existed in cache, we'd have retrieved the file from there,
258 # not built it. This can happen, though, in a race, if some
259 # other person running the same build pushes their copy to
260 # the cache after we decide we need to build it but before our
262 fs.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile)
265 fs.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
267 if not fs.isdir(cachedir):
268 fs.makedirs(cachedir)
270 tempfile = cachefile+'.tmp'
272 fs.copy2(t.path, tempfile)
273 fs.rename(tempfile, cachefile)
275 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
276 except (IOError, OSError):
277 # It's possible someone else tried writing the file at the
278 # same time we did, or else that there was some problem like
279 # the CacheDir being on a separate file system that's full.
280 # In any case, inability to push a file to cache doesn't affect
281 # the correctness of the build, so just print a warning.
282 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
283 "Unable to copy %s to cache. Cache file is %s"
284 % (str(target), cachefile))
286 CachePush = SCons.Action.Action(CachePushFunc, None)
293 DefaultSCCSBuilder = None
294 DefaultRCSBuilder = None
296 def get_DefaultSCCSBuilder():
297 global DefaultSCCSBuilder
298 if DefaultSCCSBuilder is None:
300 # "env" will get filled in by Executor.get_build_env()
301 # calling SCons.Defaults.DefaultEnvironment() when necessary.
302 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
303 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
305 name = "DefaultSCCSBuilder")
306 return DefaultSCCSBuilder
308 def get_DefaultRCSBuilder():
309 global DefaultRCSBuilder
310 if DefaultRCSBuilder is None:
312 # "env" will get filled in by Executor.get_build_env()
313 # calling SCons.Defaults.DefaultEnvironment() when necessary.
314 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
315 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
317 name = "DefaultRCSBuilder")
318 return DefaultRCSBuilder
320 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
321 _is_cygwin = sys.platform == "cygwin"
322 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
327 return string.upper(x)
332 def __init__(self, type, do, ignore):
338 self.__call__ = self.do
339 def set_ignore(self):
340 self.__call__ = self.ignore
342 if self.type in list:
347 def do_diskcheck_match(node, predicate, errorfmt):
350 # If calling the predicate() cached a None value from stat(),
351 # remove it so it doesn't interfere with later attempts to
352 # build this Node as we walk the DAG. (This isn't a great way
353 # to do this, we're reaching into an interface that doesn't
354 # really belong to us, but it's all about performance, so
355 # for now we'll just document the dependency...)
356 if node._memo['stat'] is None:
357 del node._memo['stat']
358 except (AttributeError, KeyError):
361 raise TypeError, errorfmt % node.abspath
363 def ignore_diskcheck_match(node, predicate, errorfmt):
366 def do_diskcheck_rcs(node, name):
368 rcs_dir = node.rcs_dir
369 except AttributeError:
370 if node.entry_exists_on_disk('RCS'):
371 rcs_dir = node.Dir('RCS')
374 node.rcs_dir = rcs_dir
376 return rcs_dir.entry_exists_on_disk(name+',v')
379 def ignore_diskcheck_rcs(node, name):
382 def do_diskcheck_sccs(node, name):
384 sccs_dir = node.sccs_dir
385 except AttributeError:
386 if node.entry_exists_on_disk('SCCS'):
387 sccs_dir = node.Dir('SCCS')
390 node.sccs_dir = sccs_dir
392 return sccs_dir.entry_exists_on_disk('s.'+name)
395 def ignore_diskcheck_sccs(node, name):
398 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
399 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
400 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
408 def set_diskcheck(list):
409 for dc in diskcheckers:
412 def diskcheck_types():
413 return map(lambda dc: dc.type, diskcheckers)
417 class EntryProxy(SCons.Util.Proxy):
418 def __get_abspath(self):
420 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
421 entry.name + "_abspath")
423 def __get_filebase(self):
424 name = self.get().name
425 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
428 def __get_suffix(self):
429 name = self.get().name
430 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
433 def __get_file(self):
434 name = self.get().name
435 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
437 def __get_base_path(self):
438 """Return the file's directory and file name, with the
441 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
442 entry.name + "_base")
444 def __get_posix_path(self):
445 """Return the path with / as the path separator,
446 regardless of platform."""
451 r = string.replace(entry.get_path(), os.sep, '/')
452 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
454 def __get_windows_path(self):
455 """Return the path with \ as the path separator,
456 regardless of platform."""
461 r = string.replace(entry.get_path(), os.sep, '\\')
462 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
464 def __get_srcnode(self):
465 return EntryProxy(self.get().srcnode())
467 def __get_srcdir(self):
468 """Returns the directory containing the source node linked to this
469 node via BuildDir(), or the directory of this node if not linked."""
470 return EntryProxy(self.get().srcnode().dir)
472 def __get_rsrcnode(self):
473 return EntryProxy(self.get().srcnode().rfile())
475 def __get_rsrcdir(self):
476 """Returns the directory containing the source node linked to this
477 node via BuildDir(), or the directory of this node if not linked."""
478 return EntryProxy(self.get().srcnode().rfile().dir)
481 return EntryProxy(self.get().dir)
483 dictSpecialAttrs = { "base" : __get_base_path,
484 "posix" : __get_posix_path,
485 "windows" : __get_windows_path,
486 "win32" : __get_windows_path,
487 "srcpath" : __get_srcnode,
488 "srcdir" : __get_srcdir,
490 "abspath" : __get_abspath,
491 "filebase" : __get_filebase,
492 "suffix" : __get_suffix,
494 "rsrcpath" : __get_rsrcnode,
495 "rsrcdir" : __get_rsrcdir,
498 def __getattr__(self, name):
499 # This is how we implement the "special" attributes
500 # such as base, posix, srcdir, etc.
502 attr_function = self.dictSpecialAttrs[name]
505 attr = SCons.Util.Proxy.__getattr__(self, name)
506 except AttributeError:
508 classname = string.split(str(entry.__class__), '.')[-1]
509 if classname[-2:] == "'>":
510 # new-style classes report their name as:
511 # "<class 'something'>"
512 # instead of the classic classes:
514 classname = classname[:-2]
515 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
518 return attr_function(self)
520 class Base(SCons.Node.Node):
521 """A generic class for file system entries. This class is for
522 when we don't know yet whether the entry being looked up is a file
523 or a directory. Instances of this class can morph into either
524 Dir or File objects by a later, more precise lookup.
526 Note: this class does not define __cmp__ and __hash__ for
527 efficiency reasons. SCons does a lot of comparing of
528 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
529 as fast as possible, which means we want to use Python's built-in
530 object identity comparisons.
533 memoizer_counters = []
535 def __init__(self, name, directory, fs):
536 """Initialize a generic Node.FS.Base object.
538 Call the superclass initialization, take care of setting up
539 our relative and absolute paths, identify our parent
540 directory, and indicate that this node should use
542 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
543 SCons.Node.Node.__init__(self)
546 self.suffix = SCons.Util.splitext(name)[1]
549 assert directory, "A directory must be provided"
551 self.abspath = directory.entry_abspath(name)
552 if directory.path == '.':
555 self.path = directory.entry_path(name)
556 if directory.tpath == '.':
559 self.tpath = directory.entry_tpath(name)
560 self.path_elements = directory.path_elements + [self]
563 self.cwd = None # will hold the SConscript directory for target nodes
564 self.duplicate = directory.duplicate
569 def get_suffix(self):
576 """A Node.FS.Base object's string representation is its path
580 return self._save_str()
581 return self._get_str()
583 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
587 return self._memo['_save_str']
590 result = self._get_str()
591 self._memo['_save_str'] = result
595 if self.duplicate or self.is_derived():
596 return self.get_path()
597 return self.srcnode().get_path()
601 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
604 try: return self._memo['stat']
605 except KeyError: pass
606 try: result = self.fs.stat(self.abspath)
607 except os.error: result = None
608 self._memo['stat'] = result
612 return not self.stat() is None
615 return self.rfile().exists()
619 if st: return st[stat.ST_MTIME]
624 if st: return st[stat.ST_SIZE]
629 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
633 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
635 if hasattr(os, 'symlink'):
637 try: st = self.fs.lstat(self.abspath)
638 except os.error: return 0
639 return stat.S_ISLNK(st[stat.ST_MODE])
642 return 0 # no symlinks
644 def is_under(self, dir):
648 return self.dir.is_under(dir)
654 """If this node is in a build path, return the node
655 corresponding to its source file. Otherwise, return
662 srcnode = self.fs.Entry(name, dir.srcdir,
663 klass=self.__class__)
665 name = dir.name + os.sep + name
669 def get_path(self, dir=None):
670 """Return path relative to the current working directory of the
671 Node.FS.Base object that owns us."""
673 dir = self.fs.getcwd()
676 path_elems = self.path_elements
677 try: i = path_elems.index(dir)
678 except ValueError: pass
679 else: path_elems = path_elems[i+1:]
680 path_elems = map(lambda n: n.name, path_elems)
681 return string.join(path_elems, os.sep)
683 def set_src_builder(self, builder):
684 """Set the source code builder for this node."""
685 self.sbuilder = builder
686 if not self.has_builder():
687 self.builder_set(builder)
689 def src_builder(self):
690 """Fetch the source code builder for this node.
692 If there isn't one, we cache the source code builder specified
693 for the directory (which in turn will cache the value from its
694 parent directory, and so on up to the file system root).
698 except AttributeError:
699 scb = self.dir.src_builder()
703 def get_abspath(self):
704 """Get the absolute path of the file."""
707 def for_signature(self):
708 # Return just our name. Even an absolute path would not work,
709 # because that can change thanks to symlinks or remapped network
713 def get_subst_proxy(self):
716 except AttributeError:
717 ret = EntryProxy(self)
721 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
722 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
724 def _Rfindalldirs_key(self, pathlist):
727 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
729 def Rfindalldirs(self, pathlist):
731 Return all of the directories for a given path list, including
732 corresponding "backing" directories in any repositories.
734 The Node lookups are relative to this Node (typically a
735 directory), so memoizing result saves cycles from looking
736 up the same path for each target in a given directory.
739 memo_dict = self._memo['Rfindalldirs']
742 self._memo['Rfindalldirs'] = memo_dict
745 return memo_dict[pathlist]
749 create_dir_relative_to_self = self.Dir
751 for path in pathlist:
752 if isinstance(path, SCons.Node.Node):
755 dir = create_dir_relative_to_self(path)
756 result.extend(dir.get_all_rdirs())
758 memo_dict[pathlist] = result
762 def RDirs(self, pathlist):
763 """Search for a list of directories in the Repository list."""
764 cwd = self.cwd or self.fs._cwd
765 return cwd.Rfindalldirs(pathlist)
768 """This is the class for generic Node.FS entries--that is, things
769 that could be a File or a Dir, but we're just not sure yet.
770 Consequently, the methods in this class really exist just to
771 transform their associated object into the right class when the
772 time comes, and then call the same-named method in the transformed
775 def diskcheck_match(self):
778 def disambiguate(self, must_exist=None):
785 self.__class__ = File
789 # There was nothing on-disk at this location, so look in
792 # We can't just use self.srcnode() straight away because
793 # that would create an actual Node for this file in the src
794 # directory, and there might not be one. Instead, use the
795 # dir_on_disk() method to see if there's something on-disk
796 # with that name, in which case we can go ahead and call
797 # self.srcnode() to create the right type of entry.
798 srcdir = self.dir.srcnode()
799 if srcdir != self.dir and \
800 srcdir.entry_exists_on_disk(self.name) and \
801 self.srcnode().isdir():
805 msg = "No such file or directory: '%s'" % self.abspath
806 raise SCons.Errors.UserError, msg
808 self.__class__ = File
814 """We're a generic Entry, but the caller is actually looking for
815 a File at this point, so morph into one."""
816 self.__class__ = File
819 return File.rfile(self)
821 def scanner_key(self):
822 return self.get_suffix()
824 def get_contents(self):
825 """Fetch the contents of the entry.
827 Since this should return the real contents from the file
828 system, we check to see into what sort of subclass we should
831 self = self.disambiguate(must_exist=1)
832 except SCons.Errors.UserError, e:
833 # There was nothing on disk with which to disambiguate
834 # this entry. Leave it as an Entry, but return a null
835 # string so calls to get_contents() in emitters and the
836 # like (e.g. in qt.py) don't have to disambiguate by hand
837 # or catch the exception.
840 return self.get_contents()
842 def must_be_a_Dir(self):
843 """Called to make sure a Node is a Dir. Since we're an
844 Entry, we can morph into one."""
849 # The following methods can get called before the Taskmaster has
850 # had a chance to call disambiguate() directly to see if this Entry
851 # should really be a Dir or a File. We therefore use these to call
852 # disambiguate() transparently (from our caller's point of view).
854 # Right now, this minimal set of methods has been derived by just
855 # looking at some of the methods that will obviously be called early
856 # in any of the various Taskmasters' calling sequences, and then
857 # empirically figuring out which additional methods are necessary
858 # to make various tests pass.
861 """Return if the Entry exists. Check the file system to see
862 what we should turn into first. Assume a file if there's no
864 return self.disambiguate().exists()
866 def rel_path(self, other):
867 d = self.disambiguate()
868 if d.__class__ == Entry:
869 raise "rel_path() could not disambiguate File/Dir"
870 return d.rel_path(other)
872 # This is for later so we can differentiate between Entry the class and Entry
873 # the method of the FS class.
879 if SCons.Memoize.use_memoizer:
880 __metaclass__ = SCons.Memoize.Memoized_Metaclass
882 # This class implements an abstraction layer for operations involving
883 # a local file system. Essentially, this wraps any function in
884 # the os, os.path or shutil modules that we use to actually go do
885 # anything with or to the local file system.
887 # Note that there's a very good chance we'll refactor this part of
888 # the architecture in some way as we really implement the interface(s)
889 # for remote file system Nodes. For example, the right architecture
890 # might be to have this be a subclass instead of a base class.
891 # Nevertheless, we're using this as a first step in that direction.
893 # We're not using chdir() yet because the calling subclass method
894 # needs to use os.chdir() directly to avoid recursion. Will we
895 # really need this one?
896 #def chdir(self, path):
897 # return os.chdir(path)
898 def chmod(self, path, mode):
899 return os.chmod(path, mode)
900 def copy2(self, src, dst):
901 return shutil.copy2(src, dst)
902 def exists(self, path):
903 return os.path.exists(path)
904 def getmtime(self, path):
905 return os.path.getmtime(path)
906 def getsize(self, path):
907 return os.path.getsize(path)
908 def isdir(self, path):
909 return os.path.isdir(path)
910 def isfile(self, path):
911 return os.path.isfile(path)
912 def link(self, src, dst):
913 return os.link(src, dst)
914 def lstat(self, path):
915 return os.lstat(path)
916 def listdir(self, path):
917 return os.listdir(path)
918 def makedirs(self, path):
919 return os.makedirs(path)
920 def mkdir(self, path):
921 return os.mkdir(path)
922 def rename(self, old, new):
923 return os.rename(old, new)
924 def stat(self, path):
926 def symlink(self, src, dst):
927 return os.symlink(src, dst)
928 def open(self, path):
930 def unlink(self, path):
931 return os.unlink(path)
933 if hasattr(os, 'symlink'):
934 def islink(self, path):
935 return os.path.islink(path)
937 def islink(self, path):
938 return 0 # no symlinks
940 if hasattr(os, 'readlink'):
941 def readlink(self, file):
942 return os.readlink(file)
944 def readlink(self, file):
949 # # Skeleton for the obvious methods we might need from the
950 # # abstraction layer for a remote filesystem.
951 # def upload(self, local_src, remote_dst):
953 # def download(self, remote_src, local_dst):
959 memoizer_counters = []
961 def __init__(self, path = None):
962 """Initialize the Node.FS subsystem.
964 The supplied path is the top of the source tree, where we
965 expect to find the top-level build file. If no path is
966 supplied, the current directory is the default.
968 The path argument must be a valid absolute path.
970 if __debug__: logInstanceCreation(self, 'Node.FS')
975 self.SConstruct_dir = None
976 self.CachePath = None
977 self.cache_force = None
978 self.cache_show = None
979 self.max_drift = default_max_drift
983 self.pathTop = os.getcwd()
986 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
988 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
993 def set_SConstruct_dir(self, dir):
994 self.SConstruct_dir = dir
996 def get_max_drift(self):
997 return self.max_drift
999 def set_max_drift(self, max_drift):
1000 self.max_drift = max_drift
1005 def __checkClass(self, node, klass):
1006 if isinstance(node, klass) or klass == Entry:
1008 if node.__class__ == Entry:
1009 node.__class__ = klass
1012 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
1013 (node.__class__.__name__, node.path, klass.__name__)
1015 def _doLookup_key(self, fsclass, name, directory = None, create = 1):
1016 return (fsclass, name, directory)
1018 memoizer_counters.append(SCons.Memoize.CountDict('_doLookup', _doLookup_key))
1020 def _doLookup(self, fsclass, name, directory = None, create = 1):
1021 """This method differs from the File and Dir factory methods in
1022 one important way: the meaning of the directory parameter.
1023 In this method, if directory is None or not supplied, the supplied
1024 name is expected to be an absolute path. If you try to look up a
1025 relative path with directory=None, then an AssertionError will be
1028 memo_key = (fsclass, name, directory)
1030 memo_dict = self._memo['_doLookup']
1033 self._memo['_doLookup'] = memo_dict
1036 return memo_dict[memo_key]
1041 # This is a stupid hack to compensate for the fact that the
1042 # POSIX and Windows versions of os.path.normpath() behave
1043 # differently in older versions of Python. In particular,
1045 # os.path.normpath('./') == '.'
1047 # os.path.normpath('./') == ''
1048 # os.path.normpath('.\\') == ''
1050 # This is a definite bug in the Python library, but we have
1053 path_orig = string.split(name, os.sep)
1054 path_norm = string.split(_my_normcase(name), os.sep)
1056 first_orig = path_orig.pop(0) # strip first element
1057 first_norm = path_norm.pop(0) # strip first element
1059 drive, path_first = os.path.splitdrive(first_orig)
1061 path_orig = [ path_first, ] + path_orig
1062 path_norm = [ _my_normcase(path_first), ] + path_norm
1065 drive = _my_normcase(drive)
1067 directory = self.Root[drive]
1070 raise SCons.Errors.UserError
1071 directory = RootDir(drive, self)
1072 self.Root[drive] = directory
1074 self.Root[self.defaultDrive] = directory
1075 elif drive == self.defaultDrive:
1076 self.Root[''] = directory
1079 memo_dict[memo_key] = directory
1082 last_orig = path_orig.pop() # strip last element
1083 last_norm = path_norm.pop() # strip last element
1085 # Lookup the directory
1086 for orig, norm in map(None, path_orig, path_norm):
1088 entries = directory.entries
1089 except AttributeError:
1090 # We tried to look up the entry in either an Entry or
1091 # a File. Give whatever it is a chance to do what's
1092 # appropriate: morph into a Dir or raise an exception.
1093 directory.must_be_a_Dir()
1094 entries = directory.entries
1096 directory = entries[norm]
1099 raise SCons.Errors.UserError
1101 d = Dir(orig, directory, self)
1103 # Check the file system (or not, as configured) to make
1104 # sure there isn't already a file there.
1107 directory.entries[norm] = d
1108 directory.add_wkid(d)
1111 directory.must_be_a_Dir()
1114 e = directory.entries[last_norm]
1117 raise SCons.Errors.UserError
1119 result = fsclass(last_orig, directory, self)
1121 # Check the file system (or not, as configured) to make
1122 # sure there isn't already a directory at the path on
1123 # disk where we just created a File node, and vice versa.
1124 result.diskcheck_match()
1126 directory.entries[last_norm] = result
1127 directory.add_wkid(result)
1129 result = self.__checkClass(e, fsclass)
1131 memo_dict[memo_key] = result
1135 def _transformPath(self, name, directory):
1136 """Take care of setting up the correct top-level directory,
1137 usually in preparation for a call to doLookup().
1139 If the path name is prepended with a '#', then it is unconditionally
1140 interpreted as relative to the top-level directory of this FS.
1142 If directory is None, and name is a relative path,
1143 then the same applies.
1145 if not SCons.Util.is_String(name):
1146 # This handles cases where the object is a Proxy wrapping
1147 # a Node.FS.File object (e.g.). It would be good to handle
1148 # this more directly some day by having the callers of this
1149 # function recognize that a Proxy can be treated like the
1150 # underlying object (that is, get rid of the isinstance()
1151 # calls that explicitly look for a Node.FS.Base object).
1153 if name and name[0] == '#':
1154 directory = self.Top
1156 if name and (name[0] == os.sep or name[0] == '/'):
1157 # Correct such that '#/foo' is equivalent
1160 name = os.path.normpath(os.path.join('.', name))
1161 return (name, directory)
1163 directory = self._cwd
1164 return (os.path.normpath(name), directory)
1166 def chdir(self, dir, change_os_dir=0):
1167 """Change the current working directory for lookups.
1168 If change_os_dir is true, we will also change the "real" cwd
1176 os.chdir(dir.abspath)
1181 def Entry(self, name, directory = None, create = 1, klass=None):
1182 """Lookup or create a generic Entry node with the specified name.
1183 If the name is a relative path (begins with ./, ../, or a file
1184 name), then it is looked up relative to the supplied directory
1185 node, or to the top level directory of the FS (supplied at
1186 construction time) if no directory is supplied.
1192 if isinstance(name, Base):
1193 return self.__checkClass(name, klass)
1195 if directory and not isinstance(directory, Dir):
1196 directory = self.Dir(directory)
1197 name, directory = self._transformPath(name, directory)
1198 return self._doLookup(klass, name, directory, create)
1200 def File(self, name, directory = None, create = 1):
1201 """Lookup or create a File node with the specified name. If
1202 the name is a relative path (begins with ./, ../, or a file name),
1203 then it is looked up relative to the supplied directory node,
1204 or to the top level directory of the FS (supplied at construction
1205 time) if no directory is supplied.
1207 This method will raise TypeError if a directory is found at the
1210 return self.Entry(name, directory, create, File)
1212 def Dir(self, name, directory = None, create = 1):
1213 """Lookup or create a Dir node with the specified name. If
1214 the name is a relative path (begins with ./, ../, or a file name),
1215 then it is looked up relative to the supplied directory node,
1216 or to the top level directory of the FS (supplied at construction
1217 time) if no directory is supplied.
1219 This method will raise TypeError if a normal file is found at the
1222 return self.Entry(name, directory, create, Dir)
1224 def BuildDir(self, build_dir, src_dir, duplicate=1):
1225 """Link the supplied build directory to the source directory
1226 for purposes of building files."""
1228 if not isinstance(src_dir, SCons.Node.Node):
1229 src_dir = self.Dir(src_dir)
1230 if not isinstance(build_dir, SCons.Node.Node):
1231 build_dir = self.Dir(build_dir)
1232 if src_dir.is_under(build_dir):
1233 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1234 if build_dir.srcdir:
1235 if build_dir.srcdir == src_dir:
1236 return # We already did this.
1237 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1238 build_dir.link(src_dir, duplicate)
1240 def Repository(self, *dirs):
1241 """Specify Repository directories to search."""
1243 if not isinstance(d, SCons.Node.Node):
1245 self.Top.addRepository(d)
1247 def CacheDebugWrite(self, fmt, target, cachefile):
1248 self.CacheDebugFP.write(fmt % (target, os.path.split(cachefile)[1]))
1250 def CacheDebugQuiet(self, fmt, target, cachefile):
1253 CacheDebug = CacheDebugQuiet
1255 def CacheDebugEnable(self, file):
1257 self.CacheDebugFP = sys.stdout
1259 self.CacheDebugFP = open(file, 'w')
1260 self.CacheDebug = self.CacheDebugWrite
1262 def CacheDir(self, path):
1264 import SCons.Sig.MD5
1266 msg = "No MD5 module available, CacheDir() not supported"
1267 SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
1269 self.CachePath = path
1271 def build_dir_target_climb(self, orig, dir, tail):
1272 """Create targets in corresponding build directories
1274 Climb the directory tree, and look up path names
1275 relative to any linked build directories we find.
1277 Even though this loops and walks up the tree, we don't memoize
1278 the return value because this is really only used to process
1279 the command-line targets.
1283 fmt = "building associated BuildDir targets: %s"
1286 for bd in dir.build_dirs:
1287 if start_dir.is_under(bd):
1288 # If already in the build-dir location, don't reflect
1289 return [orig], fmt % str(orig)
1290 p = apply(os.path.join, [bd.path] + tail)
1291 targets.append(self.Entry(p))
1292 tail = [dir.name] + tail
1295 message = fmt % string.join(map(str, targets))
1296 return targets, message
1298 class DirNodeInfo(SCons.Node.NodeInfoBase):
1301 class DirBuildInfo(SCons.Node.BuildInfoBase):
1305 """A class for directories in a file system.
1308 memoizer_counters = []
1310 NodeInfo = DirNodeInfo
1311 BuildInfo = DirBuildInfo
1313 def __init__(self, name, directory, fs):
1314 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1315 Base.__init__(self, name, directory, fs)
1319 """Turn a file system Node (either a freshly initialized directory
1320 object or a separate Entry object) into a proper directory object.
1322 Set up this directory's entries and hook it into the file
1323 system tree. Specify that directories (this Node) don't use
1324 signatures for calculating whether they're current.
1327 self.repositories = []
1331 self.entries['.'] = self
1332 self.entries['..'] = self.dir
1335 self._sconsign = None
1336 self.build_dirs = []
1338 # Don't just reset the executor, replace its action list,
1339 # because it might have some pre-or post-actions that need to
1341 self.builder = get_MkdirBuilder()
1342 self.get_executor().set_action_list(self.builder.action)
1344 def diskcheck_match(self):
1345 diskcheck_match(self, self.isfile,
1346 "File %s found where directory expected.")
1348 def __clearRepositoryCache(self, duplicate=None):
1349 """Called when we change the repository(ies) for a directory.
1350 This clears any cached information that is invalidated by changing
1353 for node in self.entries.values():
1354 if node != self.dir:
1355 if node != self and isinstance(node, Dir):
1356 node.__clearRepositoryCache(duplicate)
1361 except AttributeError:
1363 if duplicate != None:
1364 node.duplicate=duplicate
1366 def __resetDuplicate(self, node):
1368 node.duplicate = node.get_dir().duplicate
1370 def Entry(self, name):
1371 """Create an entry node named 'name' relative to this directory."""
1372 return self.fs.Entry(name, self)
1374 def Dir(self, name):
1375 """Create a directory node named 'name' relative to this directory."""
1376 return self.fs.Dir(name, self)
1378 def File(self, name):
1379 """Create a file node named 'name' relative to this directory."""
1380 return self.fs.File(name, self)
1382 def link(self, srcdir, duplicate):
1383 """Set this directory as the build directory for the
1384 supplied source directory."""
1385 self.srcdir = srcdir
1386 self.duplicate = duplicate
1387 self.__clearRepositoryCache(duplicate)
1388 srcdir.build_dirs.append(self)
1390 def getRepositories(self):
1391 """Returns a list of repositories for this directory.
1393 if self.srcdir and not self.duplicate:
1394 return self.srcdir.get_all_rdirs() + self.repositories
1395 return self.repositories
1397 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1399 def get_all_rdirs(self):
1401 return self._memo['get_all_rdirs']
1409 for rep in dir.getRepositories():
1410 result.append(rep.Dir(fname))
1411 fname = dir.name + os.sep + fname
1414 self._memo['get_all_rdirs'] = result
1418 def addRepository(self, dir):
1419 if dir != self and not dir in self.repositories:
1420 self.repositories.append(dir)
1422 self.__clearRepositoryCache()
1425 return self.entries['..']
1427 def _rel_path_key(self, other):
1430 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1432 def rel_path(self, other):
1433 """Return a path to "other" relative to this directory.
1436 memo_dict = self._memo['rel_path']
1439 self._memo['rel_path'] = memo_dict
1442 return memo_dict[other]
1450 elif not other in self.path_elements:
1453 other_dir = other.dir
1454 except AttributeError:
1457 dir_rel_path = self.rel_path(other_dir)
1458 if dir_rel_path == '.':
1461 result = dir_rel_path + os.sep + other.name
1465 i = self.path_elements.index(other) + 1
1467 path_elems = ['..'] * (len(self.path_elements) - i) \
1468 + map(lambda n: n.name, other.path_elements[i:])
1470 result = string.join(path_elems, os.sep)
1472 memo_dict[other] = result
1476 def get_env_scanner(self, env, kw={}):
1477 return SCons.Defaults.DirEntryScanner
1479 def get_target_scanner(self):
1480 return SCons.Defaults.DirEntryScanner
1482 def get_found_includes(self, env, scanner, path):
1483 """Return this directory's implicit dependencies.
1485 We don't bother caching the results because the scan typically
1486 shouldn't be requested more than once (as opposed to scanning
1487 .h file contents, which can be requested as many times as the
1488 files is #included by other files).
1492 # Clear cached info for this Dir. If we already visited this
1493 # directory on our walk down the tree (because we didn't know at
1494 # that point it was being used as the source for another Node)
1495 # then we may have calculated build signature before realizing
1496 # we had to scan the disk. Now that we have to, though, we need
1497 # to invalidate the old calculated signature so that any node
1498 # dependent on our directory structure gets one that includes
1499 # info about everything on disk.
1501 return scanner(self, env, path)
1503 def build(self, **kw):
1504 """A null "builder" for directories."""
1506 if not self.builder is MkdirBuilder:
1507 apply(SCons.Node.Node.build, [self,], kw)
1510 """Create this directory, silently and without worrying about
1511 whether the builder is the default or not."""
1517 listDirs.append(parent)
1520 raise SCons.Errors.StopError, parent.path
1523 for dirnode in listDirs:
1525 # Don't call dirnode.build(), call the base Node method
1526 # directly because we definitely *must* create this
1527 # directory. The dirnode.build() method will suppress
1528 # the build if it's the default builder.
1529 SCons.Node.Node.build(dirnode)
1530 dirnode.get_executor().nullify()
1531 # The build() action may or may not have actually
1532 # created the directory, depending on whether the -n
1533 # option was used or not. Delete the _exists and
1534 # _rexists attributes so they can be reevaluated.
1539 def multiple_side_effect_has_builder(self):
1541 return not self.builder is MkdirBuilder and self.has_builder()
1543 def alter_targets(self):
1544 """Return any corresponding targets in a build directory.
1546 return self.fs.build_dir_target_climb(self, self, [])
1548 def scanner_key(self):
1549 """A directory does not get scanned."""
1552 def get_contents(self):
1553 """Return aggregate contents of all our children."""
1554 contents = map(lambda n: n.get_contents(), self.children())
1555 return string.join(contents, '')
1560 def do_duplicate(self, src):
1563 def current(self, calc=None):
1564 """If any child is not up-to-date, then this directory isn't,
1566 if not self.builder is MkdirBuilder and not self.exists():
1568 up_to_date = SCons.Node.up_to_date
1569 for kid in self.children():
1570 if kid.get_state() > up_to_date:
1575 if not self.exists():
1576 norm_name = _my_normcase(self.name)
1577 for dir in self.dir.get_all_rdirs():
1578 try: node = dir.entries[norm_name]
1579 except KeyError: node = dir.dir_on_disk(self.name)
1580 if node and node.exists() and \
1581 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1586 """Return the .sconsign file info for this directory,
1587 creating it first if necessary."""
1588 if not self._sconsign:
1589 import SCons.SConsign
1590 self._sconsign = SCons.SConsign.ForDirectory(self)
1591 return self._sconsign
1594 """Dir has a special need for srcnode()...if we
1595 have a srcdir attribute set, then that *is* our srcnode."""
1598 return Base.srcnode(self)
1600 def get_timestamp(self):
1601 """Return the latest timestamp from among our children"""
1603 for kid in self.children():
1604 if kid.get_timestamp() > stamp:
1605 stamp = kid.get_timestamp()
1608 def entry_abspath(self, name):
1609 return self.abspath + os.sep + name
1611 def entry_path(self, name):
1612 return self.path + os.sep + name
1614 def entry_tpath(self, name):
1615 return self.tpath + os.sep + name
1617 def must_be_a_Dir(self):
1618 """Called to make sure a Node is a Dir. Since we're already
1619 one, this is a no-op for us."""
1622 def entry_exists_on_disk(self, name):
1624 d = self.on_disk_entries
1625 except AttributeError:
1628 entries = os.listdir(self.abspath)
1632 for entry in map(_my_normcase, entries):
1634 self.on_disk_entries = d
1635 return d.has_key(_my_normcase(name))
1637 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1639 def srcdir_list(self):
1641 return self._memo['srcdir_list']
1651 d = dir.srcdir.Dir(dirname)
1653 # Shouldn't source from something in the build path:
1654 # build_dir is probably under src_dir, in which case
1655 # we are reflecting.
1658 dirname = dir.name + os.sep + dirname
1661 self._memo['srcdir_list'] = result
1665 def srcdir_duplicate(self, name):
1666 for dir in self.srcdir_list():
1667 if dir.entry_exists_on_disk(name):
1668 srcnode = dir.File(name)
1670 node = self.File(name)
1671 node.do_duplicate(srcnode)
1677 def _srcdir_find_file_key(self, filename):
1680 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1682 def srcdir_find_file(self, filename):
1684 memo_dict = self._memo['srcdir_find_file']
1687 self._memo['srcdir_find_file'] = memo_dict
1690 return memo_dict[filename]
1695 if (isinstance(node, File) or isinstance(node, Entry)) and \
1696 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1700 norm_name = _my_normcase(filename)
1702 for rdir in self.get_all_rdirs():
1703 try: node = rdir.entries[norm_name]
1704 except KeyError: node = rdir.file_on_disk(filename)
1705 else: node = func(node)
1707 result = (node, self)
1708 memo_dict[filename] = result
1711 for srcdir in self.srcdir_list():
1712 for rdir in srcdir.get_all_rdirs():
1713 try: node = rdir.entries[norm_name]
1714 except KeyError: node = rdir.file_on_disk(filename)
1715 else: node = func(node)
1717 result = (File(filename, self, self.fs), srcdir)
1718 memo_dict[filename] = result
1721 result = (None, None)
1722 memo_dict[filename] = result
1725 def dir_on_disk(self, name):
1726 if self.entry_exists_on_disk(name):
1727 try: return self.Dir(name)
1728 except TypeError: pass
1731 def file_on_disk(self, name):
1732 if self.entry_exists_on_disk(name) or \
1733 diskcheck_rcs(self, name) or \
1734 diskcheck_sccs(self, name):
1735 try: return self.File(name)
1736 except TypeError: pass
1737 return self.srcdir_duplicate(name)
1740 """A class for the root directory of a file system.
1742 This is the same as a Dir class, except that the path separator
1743 ('/' or '\\') is actually part of the name, so we don't need to
1744 add a separator when creating the path names of entries within
1747 def __init__(self, name, fs):
1748 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1749 # We're going to be our own parent directory (".." entry and .dir
1750 # attribute) so we have to set up some values so Base.__init__()
1751 # won't gag won't it calls some of our methods.
1755 self.path_elements = []
1757 Base.__init__(self, name, self, fs)
1759 # Now set our paths to what we really want them to be: the
1760 # initial drive letter (the name) plus the directory separator.
1761 self.abspath = name + os.sep
1762 self.path = name + os.sep
1763 self.tpath = name + os.sep
1769 def entry_abspath(self, name):
1770 return self.abspath + name
1772 def entry_path(self, name):
1773 return self.path + name
1775 def entry_tpath(self, name):
1776 return self.tpath + name
1778 def is_under(self, dir):
1790 def src_builder(self):
1793 class FileNodeInfo(SCons.Node.NodeInfoBase):
1794 def __init__(self, node):
1795 SCons.Node.NodeInfoBase.__init__(self, node)
1797 def __cmp__(self, other):
1798 try: return cmp(self.bsig, other.bsig)
1799 except AttributeError: return 1
1800 def update(self, node):
1801 self.timestamp = node.get_timestamp()
1802 self.size = node.getsize()
1804 class FileBuildInfo(SCons.Node.BuildInfoBase):
1805 def __init__(self, node):
1806 SCons.Node.BuildInfoBase.__init__(self, node)
1808 def convert_to_sconsign(self):
1809 """Convert this FileBuildInfo object for writing to a .sconsign file
1811 We hung onto the node that we refer to so that we can translate
1812 the lists of bsources, bdepends and bimplicit Nodes into strings
1813 relative to the node, but we don't want to write out that Node
1814 itself to the .sconsign file, so we delete the attribute in
1817 rel_path = self.node.rel_path
1818 delattr(self, 'node')
1819 for attr in ['bsources', 'bdepends', 'bimplicit']:
1821 val = getattr(self, attr)
1822 except AttributeError:
1825 setattr(self, attr, map(rel_path, val))
1826 def convert_from_sconsign(self, dir, name):
1827 """Convert a newly-read FileBuildInfo object for in-SCons use
1829 An on-disk BuildInfo comes without a reference to the node for
1830 which it's intended, so we have to convert the arguments and add
1831 back a self.node attribute. We don't worry here about converting
1832 the bsources, bdepends and bimplicit lists from strings to Nodes
1833 because they're not used in the normal case of just deciding
1834 whether or not to rebuild things.
1836 self.node = dir.Entry(name)
1837 def prepare_dependencies(self):
1838 """Prepare a FileBuildInfo object for explaining what changed
1840 The bsources, bdepends and bimplicit lists have all been stored
1841 on disk as paths relative to the Node for which they're stored
1842 as dependency info. Convert the strings to actual Nodes (for
1843 use by the --debug=explain code and --implicit-cache).
1845 Entry_func = self.node.dir.Entry
1846 for attr in ['bsources', 'bdepends', 'bimplicit']:
1848 val = getattr(self, attr)
1849 except AttributeError:
1852 setattr(self, attr, map(Entry_func, val))
1854 result = [ self.ninfo.format() ]
1855 bkids = self.bsources + self.bdepends + self.bimplicit
1856 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
1857 for i in xrange(len(bkids)):
1858 result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
1859 return string.join(result, '\n')
1861 class NodeInfo(FileNodeInfo):
1864 class BuildInfo(FileBuildInfo):
1868 """A class for files in a file system.
1871 memoizer_counters = []
1873 NodeInfo = FileNodeInfo
1874 BuildInfo = FileBuildInfo
1876 def diskcheck_match(self):
1877 diskcheck_match(self, self.isdir,
1878 "Directory %s found where file expected.")
1880 def __init__(self, name, directory, fs):
1881 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1882 Base.__init__(self, name, directory, fs)
1885 def Entry(self, name):
1886 """Create an entry node named 'name' relative to
1887 the SConscript directory of this file."""
1888 return self.fs.Entry(name, self.cwd)
1890 def Dir(self, name):
1891 """Create a directory node named 'name' relative to
1892 the SConscript directory of this file."""
1893 return self.fs.Dir(name, self.cwd)
1895 def Dirs(self, pathlist):
1896 """Create a list of directories relative to the SConscript
1897 directory of this file."""
1898 return map(lambda p, s=self: s.Dir(p), pathlist)
1900 def File(self, name):
1901 """Create a file node named 'name' relative to
1902 the SConscript directory of this file."""
1903 return self.fs.File(name, self.cwd)
1905 #def generate_build_dict(self):
1906 # """Return an appropriate dictionary of values for building
1908 # return {'Dir' : self.Dir,
1909 # 'File' : self.File,
1910 # 'RDirs' : self.RDirs}
1913 """Turn a file system node into a File object."""
1914 self.scanner_paths = {}
1915 if not hasattr(self, '_local'):
1918 def scanner_key(self):
1919 return self.get_suffix()
1921 def get_contents(self):
1922 if not self.rexists():
1924 return open(self.rfile().abspath, "rb").read()
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 Note that there's a special trick here with the execute flag
2013 (one that's not normally done for other actions). Basically
2014 if the user requested a noexec (-n) build, then
2015 SCons.Action.execute_actions is set to 0 and when any action
2016 is called, it does its showing but then just returns zero
2017 instead of actually calling the action execution operation.
2018 The problem for caching is that if the file does NOT exist in
2019 cache then the CacheRetrieveString won't return anything to
2020 show for the task, but the Action.__call__ won't call
2021 CacheRetrieveFunc; instead it just returns zero, which makes
2022 the code below think that the file *was* successfully
2023 retrieved from the cache, therefore it doesn't do any
2024 subsequent building. However, the CacheRetrieveString didn't
2025 print anything because it didn't actually exist in the cache,
2026 and no more build actions will be performed, so the user just
2027 sees nothing. The fix is to tell Action.__call__ to always
2028 execute the CacheRetrieveFunc and then have the latter
2029 explicitly check SCons.Action.execute_actions itself.
2031 Returns true iff the node was successfully retrieved.
2033 b = self.is_derived()
2034 if not b and not self.has_src_builder():
2038 if b and self.fs.CachePath:
2039 if self.fs.cache_show:
2040 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
2041 self.build(presub=0, execute=0)
2044 if CacheRetrieve(self, [], None, execute=1) == 0:
2047 # Record build signature information, but don't
2048 # push it out to cache. (We just got it from there!)
2049 self.set_state(SCons.Node.executed)
2050 SCons.Node.Node.built(self)
2056 """Called just after this node is successfully built.
2058 # Push this file out to cache before the superclass Node.built()
2059 # method has a chance to clear the build signature, which it
2060 # will do if this file has a source scanner.
2062 # We have to clear the memoized values *before* we push it to
2063 # cache so that the memoization of the self.exists() return
2064 # value doesn't interfere.
2065 self.clear_memoized_values()
2066 if self.fs.CachePath and self.exists():
2067 CachePush(self, [], None)
2068 SCons.Node.Node.built(self)
2071 if self.fs.CachePath and self.fs.cache_force and self.exists():
2072 CachePush(self, None, None)
2074 def has_src_builder(self):
2075 """Return whether this Node has a source builder or not.
2077 If this Node doesn't have an explicit source code builder, this
2078 is where we figure out, on the fly, if there's a transparent
2079 source code builder for it.
2081 Note that if we found a source builder, we also set the
2082 self.builder attribute, so that all of the methods that actually
2083 *build* this file don't have to do anything different.
2087 except AttributeError:
2091 scb = self.dir.src_builder()
2093 if diskcheck_sccs(self.dir, self.name):
2094 scb = get_DefaultSCCSBuilder()
2095 elif diskcheck_rcs(self.dir, self.name):
2096 scb = get_DefaultRCSBuilder()
2100 self.builder_set(scb)
2102 return not scb is None
2104 def alter_targets(self):
2105 """Return any corresponding targets in a build directory.
2107 if self.is_derived():
2109 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
2111 def is_pseudo_derived(self):
2112 return self.has_src_builder()
2114 def _rmv_existing(self):
2115 self.clear_memoized_values()
2116 Unlink(self, [], None)
2119 """Prepare for this file to be created."""
2120 SCons.Node.Node.prepare(self)
2122 if self.get_state() != SCons.Node.up_to_date:
2124 if self.is_derived() and not self.precious:
2125 self._rmv_existing()
2129 except SCons.Errors.StopError, drive:
2130 desc = "No drive `%s' for target `%s'." % (drive, self)
2131 raise SCons.Errors.StopError, desc
2134 """Remove this file."""
2135 if self.exists() or self.islink():
2136 self.fs.unlink(self.path)
2140 def do_duplicate(self, src):
2143 Unlink(self, None, None)
2144 except SCons.Errors.BuildError:
2147 Link(self, src, None)
2148 except SCons.Errors.BuildError, e:
2149 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2150 raise SCons.Errors.StopError, desc
2152 # The Link() action may or may not have actually
2153 # created the file, depending on whether the -n
2154 # option was used or not. Delete the _exists and
2155 # _rexists attributes so they can be reevaluated.
2158 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2162 return self._memo['exists']
2165 # Duplicate from source path if we are set up to do this.
2166 if self.duplicate and not self.is_derived() and not self.linked:
2167 src = self.srcnode()
2169 # At this point, src is meant to be copied in a build directory.
2171 if src.abspath != self.abspath:
2173 self.do_duplicate(src)
2174 # Can't return 1 here because the duplication might
2175 # not actually occur if the -n option is being used.
2177 # The source file does not exist. Make sure no old
2178 # copy remains in the build directory.
2179 if Base.exists(self) or self.islink():
2180 self.fs.unlink(self.path)
2181 # Return None explicitly because the Base.exists() call
2182 # above will have cached its value if the file existed.
2183 self._memo['exists'] = None
2185 result = Base.exists(self)
2186 self._memo['exists'] = result
2190 # SIGNATURE SUBSYSTEM
2193 def get_csig(self, calc=None):
2195 Generate a node's content signature, the digested signature
2199 cache - alternate node to use for the signature cache
2200 returns - the content signature
2203 return self.binfo.ninfo.csig
2204 except AttributeError:
2208 calc = self.calculator()
2210 max_drift = self.fs.max_drift
2211 mtime = self.get_timestamp()
2212 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
2216 old = self.get_stored_info().ninfo
2218 if old.timestamp and old.csig and old.timestamp == mtime:
2220 except AttributeError:
2223 csig = calc.module.signature(self)
2225 binfo = self.get_binfo()
2231 self.store_info(binfo)
2239 def is_up_to_date(self, node=None, bi=None):
2240 """Returns if the node is up-to-date with respect to stored
2241 BuildInfo. The default is to compare it against our own
2242 previously stored BuildInfo, but the stored BuildInfo from another
2243 Node (typically one in a Repository) can be used instead."""
2247 bi = node.get_stored_info()
2248 new = self.get_binfo()
2251 def current(self, calc=None):
2252 self.binfo = self.gen_binfo(calc)
2255 if self.always_build:
2257 if not self.exists():
2258 # The file doesn't exist locally...
2261 # ...but there is one in a Repository...
2262 if self.is_up_to_date(r):
2263 # ...and it's even up-to-date...
2265 # ...and they'd like a local copy.
2266 LocalCopy(self, r, None)
2267 self.store_info(self.get_binfo())
2271 return self.is_up_to_date()
2273 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2277 return self._memo['rfile']
2281 if not self.exists():
2282 norm_name = _my_normcase(self.name)
2283 for dir in self.dir.get_all_rdirs():
2284 try: node = dir.entries[norm_name]
2285 except KeyError: node = dir.file_on_disk(self.name)
2286 if node and node.exists() and \
2287 (isinstance(node, File) or isinstance(node, Entry) \
2288 or not node.is_derived()):
2291 self._memo['rfile'] = result
2295 return str(self.rfile())
2297 def cachepath(self):
2298 if not self.fs.CachePath:
2300 ninfo = self.get_binfo().ninfo
2301 if not hasattr(ninfo, 'bsig'):
2303 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
2304 elif ninfo.bsig is None:
2306 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
2307 # Add the path to the cache signature, because multiple
2308 # targets built by the same action will all have the same
2309 # build signature, and we have to differentiate them somehow.
2310 import SCons.Sig.MD5
2311 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
2312 subdir = string.upper(cache_sig[0])
2313 dir = os.path.join(self.fs.CachePath, subdir)
2314 return dir, os.path.join(dir, cache_sig)
2316 def must_be_a_Dir(self):
2317 """Called to make sure a Node is a Dir. Since we're already a
2318 File, this is a TypeError..."""
2319 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
2326 if SCons.Memoize.use_memoizer:
2327 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2329 memoizer_counters = []
2334 def _find_file_key(self, filename, paths, verbose=None):
2335 return (filename, paths)
2337 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2339 def find_file(self, filename, paths, verbose=None):
2341 find_file(str, [Dir()]) -> [nodes]
2343 filename - a filename to find
2344 paths - a list of directory path *nodes* to search in. Can be
2345 represented as a list, a tuple, or a callable that is
2346 called with no arguments and returns the list or tuple.
2348 returns - the node created from the found file.
2350 Find a node corresponding to either a derived file or a file
2351 that exists already.
2353 Only the first file found is returned, and none is returned
2354 if no file is found.
2356 memo_key = self._find_file_key(filename, paths)
2358 memo_dict = self._memo['find_file']
2361 self._memo['find_file'] = memo_dict
2364 return memo_dict[memo_key]
2369 if not SCons.Util.is_String(verbose):
2370 verbose = "find_file"
2371 if not callable(verbose):
2372 verbose = ' %s: ' % verbose
2373 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2375 verbose = lambda x: x
2377 filedir, filename = os.path.split(filename)
2379 def filedir_lookup(p, fd=filedir):
2383 # We tried to look up a Dir, but it seems there's
2384 # already a File (or something else) there. No big.
2386 paths = filter(None, map(filedir_lookup, paths))
2390 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2391 node, d = dir.srcdir_find_file(filename)
2393 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2397 memo_dict[memo_key] = result
2401 find_file = FileFinder().find_file