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")
226 def CacheRetrieveFunc(target, source, env):
229 cachedir, cachefile = t.cachepath()
230 if not fs.exists(cachefile):
231 fs.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile)
233 fs.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
234 if SCons.Action.execute_actions:
235 fs.copy2(cachefile, t.path)
236 st = fs.stat(cachefile)
237 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
240 def CacheRetrieveString(target, source, env):
242 cachedir, cachefile = t.cachepath()
243 if t.fs.exists(cachefile):
244 return "Retrieved `%s' from cache" % t.path
247 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
249 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
251 def CachePushFunc(target, source, env):
254 cachedir, cachefile = t.cachepath()
255 if fs.exists(cachefile):
256 # Don't bother copying it if it's already there. Note that
257 # usually this "shouldn't happen" because if the file already
258 # existed in cache, we'd have retrieved the file from there,
259 # not built it. This can happen, though, in a race, if some
260 # other person running the same build pushes their copy to
261 # the cache after we decide we need to build it but before our
263 fs.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile)
266 fs.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
268 if not fs.isdir(cachedir):
269 fs.makedirs(cachedir)
271 tempfile = cachefile+'.tmp'
273 fs.copy2(t.path, tempfile)
274 fs.rename(tempfile, cachefile)
276 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
277 except (IOError, OSError):
278 # It's possible someone else tried writing the file at the
279 # same time we did, or else that there was some problem like
280 # the CacheDir being on a separate file system that's full.
281 # In any case, inability to push a file to cache doesn't affect
282 # the correctness of the build, so just print a warning.
283 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
284 "Unable to copy %s to cache. Cache file is %s"
285 % (str(target), cachefile))
287 CachePush = SCons.Action.Action(CachePushFunc, None)
294 DefaultSCCSBuilder = None
295 DefaultRCSBuilder = None
297 def get_DefaultSCCSBuilder():
298 global DefaultSCCSBuilder
299 if DefaultSCCSBuilder is None:
301 # "env" will get filled in by Executor.get_build_env()
302 # calling SCons.Defaults.DefaultEnvironment() when necessary.
303 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
304 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
306 name = "DefaultSCCSBuilder")
307 return DefaultSCCSBuilder
309 def get_DefaultRCSBuilder():
310 global DefaultRCSBuilder
311 if DefaultRCSBuilder is None:
313 # "env" will get filled in by Executor.get_build_env()
314 # calling SCons.Defaults.DefaultEnvironment() when necessary.
315 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
316 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
318 name = "DefaultRCSBuilder")
319 return DefaultRCSBuilder
321 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
322 _is_cygwin = sys.platform == "cygwin"
323 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
328 return string.upper(x)
333 def __init__(self, type, do, ignore):
339 self.__call__ = self.do
340 def set_ignore(self):
341 self.__call__ = self.ignore
343 if self.type in list:
348 def do_diskcheck_match(node, predicate, errorfmt):
351 raise TypeError, errorfmt % path
353 def ignore_diskcheck_match(node, predicate, errorfmt):
356 def do_diskcheck_rcs(node, name):
358 rcs_dir = node.rcs_dir
359 except AttributeError:
360 if node.entry_exists_on_disk('RCS'):
361 rcs_dir = node.Dir('RCS')
364 node.rcs_dir = rcs_dir
366 return rcs_dir.entry_exists_on_disk(name+',v')
369 def ignore_diskcheck_rcs(node, name):
372 def do_diskcheck_sccs(node, name):
374 sccs_dir = node.sccs_dir
375 except AttributeError:
376 if node.entry_exists_on_disk('SCCS'):
377 sccs_dir = node.Dir('SCCS')
380 node.sccs_dir = sccs_dir
382 return sccs_dir.entry_exists_on_disk('s.'+name)
385 def ignore_diskcheck_sccs(node, name):
388 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
389 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
390 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
398 def set_diskcheck(list):
399 for dc in diskcheckers:
402 def diskcheck_types():
403 return map(lambda dc: dc.type, diskcheckers)
407 class EntryProxy(SCons.Util.Proxy):
408 def __get_abspath(self):
410 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
411 entry.name + "_abspath")
413 def __get_filebase(self):
414 name = self.get().name
415 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
418 def __get_suffix(self):
419 name = self.get().name
420 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
423 def __get_file(self):
424 name = self.get().name
425 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
427 def __get_base_path(self):
428 """Return the file's directory and file name, with the
431 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
432 entry.name + "_base")
434 def __get_posix_path(self):
435 """Return the path with / as the path separator,
436 regardless of platform."""
441 r = string.replace(entry.get_path(), os.sep, '/')
442 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
444 def __get_windows_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 + "_windows")
454 def __get_srcnode(self):
455 return EntryProxy(self.get().srcnode())
457 def __get_srcdir(self):
458 """Returns the directory containing the source node linked to this
459 node via BuildDir(), or the directory of this node if not linked."""
460 return EntryProxy(self.get().srcnode().dir)
462 def __get_rsrcnode(self):
463 return EntryProxy(self.get().srcnode().rfile())
465 def __get_rsrcdir(self):
466 """Returns the directory containing the source node linked to this
467 node via BuildDir(), or the directory of this node if not linked."""
468 return EntryProxy(self.get().srcnode().rfile().dir)
471 return EntryProxy(self.get().dir)
473 dictSpecialAttrs = { "base" : __get_base_path,
474 "posix" : __get_posix_path,
475 "windows" : __get_windows_path,
476 "win32" : __get_windows_path,
477 "srcpath" : __get_srcnode,
478 "srcdir" : __get_srcdir,
480 "abspath" : __get_abspath,
481 "filebase" : __get_filebase,
482 "suffix" : __get_suffix,
484 "rsrcpath" : __get_rsrcnode,
485 "rsrcdir" : __get_rsrcdir,
488 def __getattr__(self, name):
489 # This is how we implement the "special" attributes
490 # such as base, posix, srcdir, etc.
492 attr_function = self.dictSpecialAttrs[name]
495 attr = SCons.Util.Proxy.__getattr__(self, name)
496 except AttributeError:
498 classname = string.split(str(entry.__class__), '.')[-1]
499 if classname[-2:] == "'>":
500 # new-style classes report their name as:
501 # "<class 'something'>"
502 # instead of the classic classes:
504 classname = classname[:-2]
505 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
508 return attr_function(self)
510 class Base(SCons.Node.Node):
511 """A generic class for file system entries. This class is for
512 when we don't know yet whether the entry being looked up is a file
513 or a directory. Instances of this class can morph into either
514 Dir or File objects by a later, more precise lookup.
516 Note: this class does not define __cmp__ and __hash__ for
517 efficiency reasons. SCons does a lot of comparing of
518 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
519 as fast as possible, which means we want to use Python's built-in
520 object identity comparisons.
523 def __init__(self, name, directory, fs):
524 """Initialize a generic Node.FS.Base object.
526 Call the superclass initialization, take care of setting up
527 our relative and absolute paths, identify our parent
528 directory, and indicate that this node should use
530 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
531 SCons.Node.Node.__init__(self)
536 assert directory, "A directory must be provided"
538 self.abspath = directory.entry_abspath(name)
539 if directory.path == '.':
542 self.path = directory.entry_path(name)
543 if directory.tpath == '.':
546 self.tpath = directory.entry_tpath(name)
547 self.path_elements = directory.path_elements + [self]
550 self.cwd = None # will hold the SConscript directory for target nodes
551 self.duplicate = directory.duplicate
554 """Completely clear a Node.FS.Base object of all its cached
555 state (so that it can be re-evaluated by interfaces that do
556 continuous integration builds).
559 SCons.Node.Node.clear(self)
564 def get_suffix(self):
566 return SCons.Util.splitext(self.name)[1]
572 """A Node.FS.Base object's string representation is its path
576 return self._save_str()
577 return self._get_str()
581 return self._get_str()
584 if self.duplicate or self.is_derived():
585 return self.get_path()
586 return self.srcnode().get_path()
592 try: return self.fs.stat(self.abspath)
593 except os.error: return None
597 return not self.stat() is None
601 return self.rfile().exists()
605 if st: return st[stat.ST_MTIME]
610 if st: return st[stat.ST_SIZE]
615 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
619 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
621 if hasattr(os, 'symlink'):
623 try: st = self.fs.lstat(self.abspath)
624 except os.error: return 0
625 return stat.S_ISLNK(st[stat.ST_MODE])
628 return 0 # no symlinks
630 def is_under(self, dir):
634 return self.dir.is_under(dir)
640 """If this node is in a build path, return the node
641 corresponding to its source file. Otherwise, return
648 srcnode = self.fs.Entry(name, dir.srcdir,
649 klass=self.__class__)
651 name = dir.name + os.sep + name
655 def get_path(self, dir=None):
656 """Return path relative to the current working directory of the
657 Node.FS.Base object that owns us."""
659 dir = self.fs.getcwd()
662 path_elems = self.path_elements
663 try: i = path_elems.index(dir)
664 except ValueError: pass
665 else: path_elems = path_elems[i+1:]
666 path_elems = map(lambda n: n.name, path_elems)
667 return string.join(path_elems, os.sep)
669 def set_src_builder(self, builder):
670 """Set the source code builder for this node."""
671 self.sbuilder = builder
672 if not self.has_builder():
673 self.builder_set(builder)
675 def src_builder(self):
676 """Fetch the source code builder for this node.
678 If there isn't one, we cache the source code builder specified
679 for the directory (which in turn will cache the value from its
680 parent directory, and so on up to the file system root).
684 except AttributeError:
685 scb = self.dir.src_builder()
689 def get_abspath(self):
690 """Get the absolute path of the file."""
693 def for_signature(self):
694 # Return just our name. Even an absolute path would not work,
695 # because that can change thanks to symlinks or remapped network
699 def get_subst_proxy(self):
702 except AttributeError:
703 ret = EntryProxy(self)
707 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
708 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
710 def RDirs(self, pathlist):
711 """Search for a list of directories in the Repository list."""
712 return self.fs.Rfindalldirs(pathlist, self.cwd)
715 """This is the class for generic Node.FS entries--that is, things
716 that could be a File or a Dir, but we're just not sure yet.
717 Consequently, the methods in this class really exist just to
718 transform their associated object into the right class when the
719 time comes, and then call the same-named method in the transformed
722 def diskcheck_match(self):
725 def disambiguate(self):
726 if self.isdir() or self.srcnode().isdir():
730 self.__class__ = File
736 """We're a generic Entry, but the caller is actually looking for
737 a File at this point, so morph into one."""
738 self.__class__ = File
741 return File.rfile(self)
743 def scanner_key(self):
744 return self.get_suffix()
746 def get_contents(self):
747 """Fetch the contents of the entry.
749 Since this should return the real contents from the file
750 system, we check to see into what sort of subclass we should
753 self.__class__ = File
755 return self.get_contents()
759 return self.get_contents()
761 return '' # avoid errors for dangling symlinks
764 def must_be_a_Dir(self):
765 """Called to make sure a Node is a Dir. Since we're an
766 Entry, we can morph into one."""
771 # The following methods can get called before the Taskmaster has
772 # had a chance to call disambiguate() directly to see if this Entry
773 # should really be a Dir or a File. We therefore use these to call
774 # disambiguate() transparently (from our caller's point of view).
776 # Right now, this minimal set of methods has been derived by just
777 # looking at some of the methods that will obviously be called early
778 # in any of the various Taskmasters' calling sequences, and then
779 # empirically figuring out which additional methods are necessary
780 # to make various tests pass.
783 """Return if the Entry exists. Check the file system to see
784 what we should turn into first. Assume a file if there's no
786 return self.disambiguate().exists()
788 def rel_path(self, other):
789 d = self.disambiguate()
790 if d.__class__ == Entry:
791 raise "rel_path() could not disambiguate File/Dir"
792 return d.rel_path(other)
794 # This is for later so we can differentiate between Entry the class and Entry
795 # the method of the FS class.
801 if SCons.Memoize.use_memoizer:
802 __metaclass__ = SCons.Memoize.Memoized_Metaclass
804 # This class implements an abstraction layer for operations involving
805 # a local file system. Essentially, this wraps any function in
806 # the os, os.path or shutil modules that we use to actually go do
807 # anything with or to the local file system.
809 # Note that there's a very good chance we'll refactor this part of
810 # the architecture in some way as we really implement the interface(s)
811 # for remote file system Nodes. For example, the right architecture
812 # might be to have this be a subclass instead of a base class.
813 # Nevertheless, we're using this as a first step in that direction.
815 # We're not using chdir() yet because the calling subclass method
816 # needs to use os.chdir() directly to avoid recursion. Will we
817 # really need this one?
818 #def chdir(self, path):
819 # return os.chdir(path)
820 def chmod(self, path, mode):
821 return os.chmod(path, mode)
822 def copy2(self, src, dst):
823 return shutil.copy2(src, dst)
824 def exists(self, path):
825 return os.path.exists(path)
826 def getmtime(self, path):
827 return os.path.getmtime(path)
828 def getsize(self, path):
829 return os.path.getsize(path)
830 def isdir(self, path):
831 return os.path.isdir(path)
832 def isfile(self, path):
833 return os.path.isfile(path)
834 def link(self, src, dst):
835 return os.link(src, dst)
836 def lstat(self, path):
837 return os.lstat(path)
838 def listdir(self, path):
839 return os.listdir(path)
840 def makedirs(self, path):
841 return os.makedirs(path)
842 def mkdir(self, path):
843 return os.mkdir(path)
844 def rename(self, old, new):
845 return os.rename(old, new)
846 def stat(self, path):
848 def symlink(self, src, dst):
849 return os.symlink(src, dst)
850 def open(self, path):
852 def unlink(self, path):
853 return os.unlink(path)
855 if hasattr(os, 'symlink'):
856 def islink(self, path):
857 return os.path.islink(path)
859 def islink(self, path):
860 return 0 # no symlinks
862 if hasattr(os, 'readlink'):
863 def readlink(self, file):
864 return os.readlink(file)
866 def readlink(self, file):
870 if SCons.Memoize.use_old_memoization():
872 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
873 def __init__(self, *args, **kw):
874 apply(_FSBase.__init__, (self,)+args, kw)
875 SCons.Memoize.Memoizer.__init__(self)
879 # # Skeleton for the obvious methods we might need from the
880 # # abstraction layer for a remote filesystem.
881 # def upload(self, local_src, remote_dst):
883 # def download(self, remote_src, local_dst):
889 def __init__(self, path = None):
890 """Initialize the Node.FS subsystem.
892 The supplied path is the top of the source tree, where we
893 expect to find the top-level build file. If no path is
894 supplied, the current directory is the default.
896 The path argument must be a valid absolute path.
898 if __debug__: logInstanceCreation(self, 'Node.FS')
901 self.SConstruct_dir = None
902 self.CachePath = None
903 self.cache_force = None
904 self.cache_show = None
905 self.max_drift = default_max_drift
909 self.pathTop = os.getcwd()
912 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
914 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
919 def clear_cache(self):
923 def set_SConstruct_dir(self, dir):
924 self.SConstruct_dir = dir
926 def get_max_drift(self):
927 return self.max_drift
929 def set_max_drift(self, max_drift):
930 self.max_drift = max_drift
935 def __checkClass(self, node, klass):
936 if isinstance(node, klass) or klass == Entry:
938 if node.__class__ == Entry:
939 node.__class__ = klass
942 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
943 (node.__class__.__name__, node.path, klass.__name__)
945 def _doLookup(self, fsclass, name, directory = None, create = 1):
946 """This method differs from the File and Dir factory methods in
947 one important way: the meaning of the directory parameter.
948 In this method, if directory is None or not supplied, the supplied
949 name is expected to be an absolute path. If you try to look up a
950 relative path with directory=None, then an AssertionError will be
955 # This is a stupid hack to compensate for the fact that the
956 # POSIX and Windows versions of os.path.normpath() behave
957 # differently in older versions of Python. In particular,
959 # os.path.normpath('./') == '.'
961 # os.path.normpath('./') == ''
962 # os.path.normpath('.\\') == ''
964 # This is a definite bug in the Python library, but we have
967 path_orig = string.split(name, os.sep)
968 path_norm = string.split(_my_normcase(name), os.sep)
970 first_orig = path_orig.pop(0) # strip first element
971 first_norm = path_norm.pop(0) # strip first element
973 drive, path_first = os.path.splitdrive(first_orig)
975 path_orig = [ path_first, ] + path_orig
976 path_norm = [ _my_normcase(path_first), ] + path_norm
979 drive = _my_normcase(drive)
981 directory = self.Root[drive]
984 raise SCons.Errors.UserError
985 directory = RootDir(drive, self)
986 self.Root[drive] = directory
988 self.Root[self.defaultDrive] = directory
989 elif drive == self.defaultDrive:
990 self.Root[''] = directory
995 last_orig = path_orig.pop() # strip last element
996 last_norm = path_norm.pop() # strip last element
998 # Lookup the directory
999 for orig, norm in map(None, path_orig, path_norm):
1001 entries = directory.entries
1002 except AttributeError:
1003 # We tried to look up the entry in either an Entry or
1004 # a File. Give whatever it is a chance to do what's
1005 # appropriate: morph into a Dir or raise an exception.
1006 directory.must_be_a_Dir()
1007 entries = directory.entries
1009 directory = entries[norm]
1012 raise SCons.Errors.UserError
1014 d = Dir(orig, directory, self)
1016 # Check the file system (or not, as configured) to make
1017 # sure there isn't already a file there.
1020 directory.entries[norm] = d
1021 directory.add_wkid(d)
1024 directory.must_be_a_Dir()
1027 e = directory.entries[last_norm]
1030 raise SCons.Errors.UserError
1032 result = fsclass(last_orig, directory, self)
1034 # Check the file system (or not, as configured) to make
1035 # sure there isn't already a directory at the path on
1036 # disk where we just created a File node, and vice versa.
1037 result.diskcheck_match()
1039 directory.entries[last_norm] = result
1040 directory.add_wkid(result)
1042 result = self.__checkClass(e, fsclass)
1045 def _transformPath(self, name, directory):
1046 """Take care of setting up the correct top-level directory,
1047 usually in preparation for a call to doLookup().
1049 If the path name is prepended with a '#', then it is unconditionally
1050 interpreted as relative to the top-level directory of this FS.
1052 If directory is None, and name is a relative path,
1053 then the same applies.
1055 if not SCons.Util.is_String(name):
1056 # This handles cases where the object is a Proxy wrapping
1057 # a Node.FS.File object (e.g.). It would be good to handle
1058 # this more directly some day by having the callers of this
1059 # function recognize that a Proxy can be treated like the
1060 # underlying object (that is, get rid of the isinstance()
1061 # calls that explicitly look for a Node.FS.Base object).
1063 if name and name[0] == '#':
1064 directory = self.Top
1066 if name and (name[0] == os.sep or name[0] == '/'):
1067 # Correct such that '#/foo' is equivalent
1070 name = os.path.join('.', os.path.normpath(name))
1072 directory = self._cwd
1073 return (os.path.normpath(name), directory)
1075 def chdir(self, dir, change_os_dir=0):
1076 """Change the current working directory for lookups.
1077 If change_os_dir is true, we will also change the "real" cwd
1085 os.chdir(dir.abspath)
1090 def Entry(self, name, directory = None, create = 1, klass=None):
1091 """Lookup or create a generic Entry node with the specified name.
1092 If the name is a relative path (begins with ./, ../, or a file
1093 name), then it is looked up relative to the supplied directory
1094 node, or to the top level directory of the FS (supplied at
1095 construction time) if no directory is supplied.
1101 if isinstance(name, Base):
1102 return self.__checkClass(name, klass)
1104 if directory and not isinstance(directory, Dir):
1105 directory = self.Dir(directory)
1106 name, directory = self._transformPath(name, directory)
1107 return self._doLookup(klass, name, directory, create)
1109 def File(self, name, directory = None, create = 1):
1110 """Lookup or create a File node with the specified name. If
1111 the name is a relative path (begins with ./, ../, or a file name),
1112 then it is looked up relative to the supplied directory node,
1113 or to the top level directory of the FS (supplied at construction
1114 time) if no directory is supplied.
1116 This method will raise TypeError if a directory is found at the
1120 return self.Entry(name, directory, create, File)
1122 def Dir(self, name, directory = None, create = 1):
1123 """Lookup or create a Dir node with the specified name. If
1124 the name is a relative path (begins with ./, ../, or a file name),
1125 then it is looked up relative to the supplied directory node,
1126 or to the top level directory of the FS (supplied at construction
1127 time) if no directory is supplied.
1129 This method will raise TypeError if a normal file is found at the
1133 return self.Entry(name, directory, create, Dir)
1135 def BuildDir(self, build_dir, src_dir, duplicate=1):
1136 """Link the supplied build directory to the source directory
1137 for purposes of building files."""
1139 if not isinstance(src_dir, SCons.Node.Node):
1140 src_dir = self.Dir(src_dir)
1141 if not isinstance(build_dir, SCons.Node.Node):
1142 build_dir = self.Dir(build_dir)
1143 if src_dir.is_under(build_dir):
1144 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1145 if build_dir.srcdir:
1146 if build_dir.srcdir == src_dir:
1147 return # We already did this.
1148 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1149 build_dir.link(src_dir, duplicate)
1151 def Repository(self, *dirs):
1152 """Specify Repository directories to search."""
1154 if not isinstance(d, SCons.Node.Node):
1156 self.Top.addRepository(d)
1158 def Rfindalldirs(self, pathlist, cwd):
1160 if SCons.Util.is_String(pathlist):
1161 pathlist = string.split(pathlist, os.pathsep)
1162 if not SCons.Util.is_List(pathlist):
1163 pathlist = [pathlist]
1165 for path in filter(None, pathlist):
1166 if isinstance(path, SCons.Node.Node):
1169 path, dir = self._transformPath(path, cwd)
1171 result.extend(dir.get_all_rdirs())
1174 def CacheDebugWrite(self, fmt, target, cachefile):
1175 self.CacheDebugFP.write(fmt % (target, os.path.split(cachefile)[1]))
1177 def CacheDebugQuiet(self, fmt, target, cachefile):
1180 CacheDebug = CacheDebugQuiet
1182 def CacheDebugEnable(self, file):
1184 self.CacheDebugFP = sys.stdout
1186 self.CacheDebugFP = open(file, 'w')
1187 self.CacheDebug = self.CacheDebugWrite
1189 def CacheDir(self, path):
1190 self.CachePath = path
1192 def build_dir_target_climb(self, orig, dir, tail):
1193 """Create targets in corresponding build directories
1195 Climb the directory tree, and look up path names
1196 relative to any linked build directories we find.
1201 fmt = "building associated BuildDir targets: %s"
1204 for bd in dir.build_dirs:
1205 if start_dir.is_under(bd):
1206 # If already in the build-dir location, don't reflect
1207 return [orig], fmt % str(orig)
1208 p = apply(os.path.join, [bd.path] + tail)
1209 targets.append(self.Entry(p))
1210 tail = [dir.name] + tail
1213 message = fmt % string.join(map(str, targets))
1214 return targets, message
1216 class DirNodeInfo(SCons.Node.NodeInfoBase):
1219 class DirBuildInfo(SCons.Node.BuildInfoBase):
1223 """A class for directories in a file system.
1226 NodeInfo = DirNodeInfo
1227 BuildInfo = DirBuildInfo
1229 def __init__(self, name, directory, fs):
1230 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1231 Base.__init__(self, name, directory, fs)
1235 """Turn a file system Node (either a freshly initialized directory
1236 object or a separate Entry object) into a proper directory object.
1238 Set up this directory's entries and hook it into the file
1239 system tree. Specify that directories (this Node) don't use
1240 signatures for calculating whether they're current.
1243 self.repositories = []
1247 self.entries['.'] = self
1248 self.entries['..'] = self.dir
1251 self._sconsign = None
1252 self.build_dirs = []
1254 # Don't just reset the executor, replace its action list,
1255 # because it might have some pre-or post-actions that need to
1257 self.builder = get_MkdirBuilder()
1258 self.get_executor().set_action_list(self.builder.action)
1260 def diskcheck_match(self):
1261 diskcheck_match(self, self.fs.isfile,
1262 "File %s found where directory expected.")
1264 def __clearRepositoryCache(self, duplicate=None):
1265 """Called when we change the repository(ies) for a directory.
1266 This clears any cached information that is invalidated by changing
1269 for node in self.entries.values():
1270 if node != self.dir:
1271 if node != self and isinstance(node, Dir):
1272 node.__clearRepositoryCache(duplicate)
1277 except AttributeError:
1279 if duplicate != None:
1280 node.duplicate=duplicate
1282 def __resetDuplicate(self, node):
1284 node.duplicate = node.get_dir().duplicate
1286 def Entry(self, name):
1287 """Create an entry node named 'name' relative to this directory."""
1288 return self.fs.Entry(name, self)
1290 def Dir(self, name):
1291 """Create a directory node named 'name' relative to this directory."""
1292 return self.fs.Dir(name, self)
1294 def File(self, name):
1295 """Create a file node named 'name' relative to this directory."""
1296 return self.fs.File(name, self)
1298 def link(self, srcdir, duplicate):
1299 """Set this directory as the build directory for the
1300 supplied source directory."""
1301 self.srcdir = srcdir
1302 self.duplicate = duplicate
1303 self.__clearRepositoryCache(duplicate)
1304 srcdir.build_dirs.append(self)
1306 def getRepositories(self):
1307 """Returns a list of repositories for this directory.
1309 if self.srcdir and not self.duplicate:
1310 return self.srcdir.get_all_rdirs() + self.repositories
1311 return self.repositories
1313 def get_all_rdirs(self):
1319 for rep in dir.getRepositories():
1320 result.append(rep.Dir(fname))
1321 fname = dir.name + os.sep + fname
1325 def addRepository(self, dir):
1326 if dir != self and not dir in self.repositories:
1327 self.repositories.append(dir)
1329 self.__clearRepositoryCache()
1332 return self.entries['..']
1334 def rel_path(self, other):
1335 """Return a path to "other" relative to this directory.
1337 if isinstance(other, Dir):
1343 except AttributeError:
1346 return name and name[0] or '.'
1348 for x, y in map(None, self.path_elements, other.path_elements):
1352 path_elems = ['..']*(len(self.path_elements)-i) \
1353 + map(lambda n: n.name, other.path_elements[i:]) \
1356 return string.join(path_elems, os.sep)
1358 def get_env_scanner(self, env, kw={}):
1359 return SCons.Defaults.DirEntryScanner
1361 def get_target_scanner(self):
1362 return SCons.Defaults.DirEntryScanner
1364 def get_found_includes(self, env, scanner, path):
1365 """Return the included implicit dependencies in this file.
1366 Cache results so we only scan the file once per path
1367 regardless of how many times this information is requested.
1371 # Clear cached info for this Dir. If we already visited this
1372 # directory on our walk down the tree (because we didn't know at
1373 # that point it was being used as the source for another Node)
1374 # then we may have calculated build signature before realizing
1375 # we had to scan the disk. Now that we have to, though, we need
1376 # to invalidate the old calculated signature so that any node
1377 # dependent on our directory structure gets one that includes
1378 # info about everything on disk.
1380 return scanner(self, env, path)
1382 def build(self, **kw):
1383 """A null "builder" for directories."""
1385 if not self.builder is MkdirBuilder:
1386 apply(SCons.Node.Node.build, [self,], kw)
1389 """Create this directory, silently and without worrying about
1390 whether the builder is the default or not."""
1396 listDirs.append(parent)
1399 raise SCons.Errors.StopError, parent.path
1402 for dirnode in listDirs:
1404 # Don't call dirnode.build(), call the base Node method
1405 # directly because we definitely *must* create this
1406 # directory. The dirnode.build() method will suppress
1407 # the build if it's the default builder.
1408 SCons.Node.Node.build(dirnode)
1409 dirnode.get_executor().nullify()
1410 # The build() action may or may not have actually
1411 # created the directory, depending on whether the -n
1412 # option was used or not. Delete the _exists and
1413 # _rexists attributes so they can be reevaluated.
1418 def multiple_side_effect_has_builder(self):
1420 return not self.builder is MkdirBuilder and self.has_builder()
1422 def alter_targets(self):
1423 """Return any corresponding targets in a build directory.
1425 return self.fs.build_dir_target_climb(self, self, [])
1427 def scanner_key(self):
1428 """A directory does not get scanned."""
1431 def get_contents(self):
1432 """Return aggregate contents of all our children."""
1433 contents = map(lambda n: n.get_contents(), self.children())
1434 return string.join(contents, '')
1439 def do_duplicate(self, src):
1442 def current(self, calc=None):
1443 """If any child is not up-to-date, then this directory isn't,
1445 if not self.builder is MkdirBuilder and not self.exists():
1447 up_to_date = SCons.Node.up_to_date
1448 for kid in self.children():
1449 if kid.get_state() > up_to_date:
1455 if not self.exists():
1456 norm_name = _my_normcase(self.name)
1457 for dir in self.dir.get_all_rdirs():
1458 try: node = dir.entries[norm_name]
1459 except KeyError: node = dir.dir_on_disk(self.name)
1460 if node and node.exists() and \
1461 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1466 """Return the .sconsign file info for this directory,
1467 creating it first if necessary."""
1468 if not self._sconsign:
1469 import SCons.SConsign
1470 self._sconsign = SCons.SConsign.ForDirectory(self)
1471 return self._sconsign
1474 """Dir has a special need for srcnode()...if we
1475 have a srcdir attribute set, then that *is* our srcnode."""
1478 return Base.srcnode(self)
1480 def get_timestamp(self):
1481 """Return the latest timestamp from among our children"""
1483 for kid in self.children():
1484 if kid.get_timestamp() > stamp:
1485 stamp = kid.get_timestamp()
1488 def entry_abspath(self, name):
1489 return self.abspath + os.sep + name
1491 def entry_path(self, name):
1492 return self.path + os.sep + name
1494 def entry_tpath(self, name):
1495 return self.tpath + os.sep + name
1497 def must_be_a_Dir(self):
1498 """Called to make sure a Node is a Dir. Since we're already
1499 one, this is a no-op for us."""
1502 def entry_exists_on_disk(self, name):
1505 d = self.on_disk_entries
1506 except AttributeError:
1509 entries = os.listdir(self.abspath)
1513 for entry in map(_my_normcase, entries):
1515 self.on_disk_entries = d
1516 return d.has_key(_my_normcase(name))
1518 def srcdir_list(self):
1526 d = dir.srcdir.Dir(dirname)
1528 # Shouldn't source from something in the build path:
1529 # build_dir is probably under src_dir, in which case
1530 # we are reflecting.
1533 dirname = dir.name + os.sep + dirname
1538 def srcdir_duplicate(self, name):
1539 for dir in self.srcdir_list():
1540 if dir.entry_exists_on_disk(name):
1541 srcnode = dir.File(name)
1543 node = self.File(name)
1544 node.do_duplicate(srcnode)
1550 def srcdir_find_file(self, filename):
1553 if (isinstance(node, File) or isinstance(node, Entry)) and \
1554 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1558 norm_name = _my_normcase(filename)
1560 for rdir in self.get_all_rdirs():
1561 try: node = rdir.entries[norm_name]
1562 except KeyError: node = rdir.file_on_disk(filename)
1563 else: node = func(node)
1567 for srcdir in self.srcdir_list():
1568 for rdir in srcdir.get_all_rdirs():
1569 try: node = rdir.entries[norm_name]
1570 except KeyError: node = rdir.file_on_disk(filename)
1571 else: node = func(node)
1573 return File(filename, self, self.fs), srcdir
1577 def dir_on_disk(self, name):
1578 if self.entry_exists_on_disk(name):
1579 try: return self.Dir(name)
1580 except TypeError: pass
1583 def file_on_disk(self, name):
1584 if self.entry_exists_on_disk(name) or \
1585 diskcheck_rcs(self, name) or \
1586 diskcheck_sccs(self, name):
1587 try: return self.File(name)
1588 except TypeError: pass
1589 return self.srcdir_duplicate(name)
1592 """A class for the root directory of a file system.
1594 This is the same as a Dir class, except that the path separator
1595 ('/' or '\\') is actually part of the name, so we don't need to
1596 add a separator when creating the path names of entries within
1599 def __init__(self, name, fs):
1600 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1601 # We're going to be our own parent directory (".." entry and .dir
1602 # attribute) so we have to set up some values so Base.__init__()
1603 # won't gag won't it calls some of our methods.
1607 self.path_elements = []
1609 Base.__init__(self, name, self, fs)
1611 # Now set our paths to what we really want them to be: the
1612 # initial drive letter (the name) plus the directory separator.
1613 self.abspath = name + os.sep
1614 self.path = name + os.sep
1615 self.tpath = name + os.sep
1621 def entry_abspath(self, name):
1622 return self.abspath + name
1624 def entry_path(self, name):
1625 return self.path + name
1627 def entry_tpath(self, name):
1628 return self.tpath + name
1630 def is_under(self, dir):
1642 def src_builder(self):
1645 class FileNodeInfo(SCons.Node.NodeInfoBase):
1646 def __init__(self, node):
1647 SCons.Node.NodeInfoBase.__init__(self, node)
1649 def __cmp__(self, other):
1650 try: return cmp(self.bsig, other.bsig)
1651 except AttributeError: return 1
1652 def update(self, node):
1653 self.timestamp = node.get_timestamp()
1654 self.size = node.getsize()
1656 class FileBuildInfo(SCons.Node.BuildInfoBase):
1657 def __init__(self, node):
1658 SCons.Node.BuildInfoBase.__init__(self, node)
1660 def convert_to_sconsign(self):
1661 """Convert this FileBuildInfo object for writing to a .sconsign file
1663 We hung onto the node that we refer to so that we can translate
1664 the lists of bsources, bdepends and bimplicit Nodes into strings
1665 relative to the node, but we don't want to write out that Node
1666 itself to the .sconsign file, so we delete the attribute in
1669 rel_path = self.node.rel_path
1670 delattr(self, 'node')
1671 for attr in ['bsources', 'bdepends', 'bimplicit']:
1673 val = getattr(self, attr)
1674 except AttributeError:
1677 setattr(self, attr, map(rel_path, val))
1678 def convert_from_sconsign(self, dir, name):
1679 """Convert a newly-read FileBuildInfo object for in-SCons use
1681 An on-disk BuildInfo comes without a reference to the node for
1682 which it's intended, so we have to convert the arguments and add
1683 back a self.node attribute. We don't worry here about converting
1684 the bsources, bdepends and bimplicit lists from strings to Nodes
1685 because they're not used in the normal case of just deciding
1686 whether or not to rebuild things.
1688 self.node = dir.Entry(name)
1689 def prepare_dependencies(self):
1690 """Prepare a FileBuildInfo object for explaining what changed
1692 The bsources, bdepends and bimplicit lists have all been stored
1693 on disk as paths relative to the Node for which they're stored
1694 as dependency info. Convert the strings to actual Nodes (for
1695 use by the --debug=explain code and --implicit-cache).
1697 Entry_func = self.node.dir.Entry
1698 for attr in ['bsources', 'bdepends', 'bimplicit']:
1700 val = getattr(self, attr)
1701 except AttributeError:
1704 setattr(self, attr, map(Entry_func, val))
1706 result = [ self.ninfo.format() ]
1707 bkids = self.bsources + self.bdepends + self.bimplicit
1708 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
1709 for i in xrange(len(bkids)):
1710 result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
1711 return string.join(result, '\n')
1713 class NodeInfo(FileNodeInfo):
1716 class BuildInfo(FileBuildInfo):
1720 """A class for files in a file system.
1723 NodeInfo = FileNodeInfo
1724 BuildInfo = FileBuildInfo
1726 def diskcheck_match(self):
1727 diskcheck_match(self, self.fs.isdir,
1728 "Directory %s found where file expected.")
1730 def __init__(self, name, directory, fs):
1731 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1732 Base.__init__(self, name, directory, fs)
1735 def Entry(self, name):
1736 """Create an entry node named 'name' relative to
1737 the SConscript directory of this file."""
1738 return self.fs.Entry(name, self.cwd)
1740 def Dir(self, name):
1741 """Create a directory node named 'name' relative to
1742 the SConscript directory of this file."""
1743 return self.fs.Dir(name, self.cwd)
1745 def Dirs(self, pathlist):
1746 """Create a list of directories relative to the SConscript
1747 directory of this file."""
1748 return map(lambda p, s=self: s.Dir(p), pathlist)
1750 def File(self, name):
1751 """Create a file node named 'name' relative to
1752 the SConscript directory of this file."""
1753 return self.fs.File(name, self.cwd)
1755 #def generate_build_dict(self):
1756 # """Return an appropriate dictionary of values for building
1758 # return {'Dir' : self.Dir,
1759 # 'File' : self.File,
1760 # 'RDirs' : self.RDirs}
1763 """Turn a file system node into a File object. __cache_reset__"""
1764 self.scanner_paths = {}
1765 if not hasattr(self, '_local'):
1768 def scanner_key(self):
1769 return self.get_suffix()
1771 def get_contents(self):
1772 if not self.rexists():
1774 return open(self.rfile().abspath, "rb").read()
1776 def get_timestamp(self):
1778 return self.rfile().getmtime()
1782 def store_info(self, obj):
1783 # Merge our build information into the already-stored entry.
1784 # This accomodates "chained builds" where a file that's a target
1785 # in one build (SConstruct file) is a source in a different build.
1786 # See test/chained-build.py for the use case.
1787 entry = self.get_stored_info()
1789 self.dir.sconsign().set_entry(self.name, entry)
1791 def get_stored_info(self):
1794 stored = self.dir.sconsign().get_entry(self.name)
1795 except (KeyError, OSError):
1796 return self.new_binfo()
1798 if not hasattr(stored, 'ninfo'):
1799 # Transition: The .sconsign file entry has no NodeInfo
1800 # object, which means it's a slightly older BuildInfo.
1801 # Copy over the relevant attributes.
1802 ninfo = stored.ninfo = self.new_ninfo()
1803 for attr in ninfo.__dict__.keys():
1805 setattr(ninfo, attr, getattr(stored, attr))
1806 except AttributeError:
1810 def get_stored_implicit(self):
1811 binfo = self.get_stored_info()
1812 binfo.prepare_dependencies()
1813 try: return binfo.bimplicit
1814 except AttributeError: return None
1816 def rel_path(self, other):
1817 return self.dir.rel_path(other)
1819 def get_found_includes(self, env, scanner, path):
1820 """Return the included implicit dependencies in this file.
1821 Cache results so we only scan the file once per path
1822 regardless of how many times this information is requested.
1826 return scanner(self, env, path)
1828 def _createDir(self):
1829 # ensure that the directories for this node are
1833 def retrieve_from_cache(self):
1834 """Try to retrieve the node's content from a cache
1836 This method is called from multiple threads in a parallel build,
1837 so only do thread safe stuff here. Do thread unsafe stuff in
1840 Note that there's a special trick here with the execute flag
1841 (one that's not normally done for other actions). Basically
1842 if the user requested a noexec (-n) build, then
1843 SCons.Action.execute_actions is set to 0 and when any action
1844 is called, it does its showing but then just returns zero
1845 instead of actually calling the action execution operation.
1846 The problem for caching is that if the file does NOT exist in
1847 cache then the CacheRetrieveString won't return anything to
1848 show for the task, but the Action.__call__ won't call
1849 CacheRetrieveFunc; instead it just returns zero, which makes
1850 the code below think that the file *was* successfully
1851 retrieved from the cache, therefore it doesn't do any
1852 subsequent building. However, the CacheRetrieveString didn't
1853 print anything because it didn't actually exist in the cache,
1854 and no more build actions will be performed, so the user just
1855 sees nothing. The fix is to tell Action.__call__ to always
1856 execute the CacheRetrieveFunc and then have the latter
1857 explicitly check SCons.Action.execute_actions itself.
1859 Returns true iff the node was successfully retrieved.
1861 b = self.is_derived()
1862 if not b and not self.has_src_builder():
1864 if b and self.fs.CachePath:
1865 if self.fs.cache_show:
1866 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1867 self.build(presub=0, execute=0)
1868 self.set_state(SCons.Node.executed)
1870 elif CacheRetrieve(self, [], None, execute=1) == 0:
1871 self.set_state(SCons.Node.executed)
1877 """Called just after this node is successfully built.
1879 # Push this file out to cache before the superclass Node.built()
1880 # method has a chance to clear the build signature, which it
1881 # will do if this file has a source scanner.
1882 if self.fs.CachePath and self.exists():
1883 CachePush(self, [], None)
1884 self.fs.clear_cache()
1885 SCons.Node.Node.built(self)
1888 if self.fs.CachePath and self.fs.cache_force and self.exists():
1889 CachePush(self, None, None)
1891 def has_src_builder(self):
1892 """Return whether this Node has a source builder or not.
1894 If this Node doesn't have an explicit source code builder, this
1895 is where we figure out, on the fly, if there's a transparent
1896 source code builder for it.
1898 Note that if we found a source builder, we also set the
1899 self.builder attribute, so that all of the methods that actually
1900 *build* this file don't have to do anything different.
1904 except AttributeError:
1908 scb = self.dir.src_builder()
1910 if diskcheck_sccs(self.dir, self.name):
1911 scb = get_DefaultSCCSBuilder()
1912 elif diskcheck_rcs(self.dir, self.name):
1913 scb = get_DefaultRCSBuilder()
1917 self.builder_set(scb)
1919 return not scb is None
1921 def alter_targets(self):
1922 """Return any corresponding targets in a build directory.
1924 if self.is_derived():
1926 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1928 def is_pseudo_derived(self):
1930 return self.has_src_builder()
1932 def _rmv_existing(self):
1934 Unlink(self, [], None)
1937 """Prepare for this file to be created."""
1938 SCons.Node.Node.prepare(self)
1940 if self.get_state() != SCons.Node.up_to_date:
1942 if self.is_derived() and not self.precious:
1943 self._rmv_existing()
1947 except SCons.Errors.StopError, drive:
1948 desc = "No drive `%s' for target `%s'." % (drive, self)
1949 raise SCons.Errors.StopError, desc
1952 """Remove this file."""
1953 if self.exists() or self.islink():
1954 self.fs.unlink(self.path)
1958 def do_duplicate(self, src):
1961 Unlink(self, None, None)
1962 except SCons.Errors.BuildError:
1965 Link(self, src, None)
1966 except SCons.Errors.BuildError, e:
1967 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1968 raise SCons.Errors.StopError, desc
1970 # The Link() action may or may not have actually
1971 # created the file, depending on whether the -n
1972 # option was used or not. Delete the _exists and
1973 # _rexists attributes so they can be reevaluated.
1978 # Duplicate from source path if we are set up to do this.
1979 if self.duplicate and not self.is_derived() and not self.linked:
1980 src = self.srcnode()
1982 return Base.exists(self)
1983 # At this point, src is meant to be copied in a build directory.
1985 if src.abspath != self.abspath:
1987 self.do_duplicate(src)
1988 # Can't return 1 here because the duplication might
1989 # not actually occur if the -n option is being used.
1991 # The source file does not exist. Make sure no old
1992 # copy remains in the build directory.
1993 if Base.exists(self) or self.islink():
1994 self.fs.unlink(self.path)
1995 # Return None explicitly because the Base.exists() call
1996 # above will have cached its value if the file existed.
1998 return Base.exists(self)
2001 # SIGNATURE SUBSYSTEM
2004 def get_csig(self, calc=None):
2006 Generate a node's content signature, the digested signature
2010 cache - alternate node to use for the signature cache
2011 returns - the content signature
2014 return self.binfo.ninfo.csig
2015 except AttributeError:
2019 calc = self.calculator()
2021 max_drift = self.fs.max_drift
2022 mtime = self.get_timestamp()
2023 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
2027 old = self.get_stored_info().ninfo
2029 if old.timestamp and old.csig and old.timestamp == mtime:
2031 except AttributeError:
2034 csig = calc.module.signature(self)
2036 binfo = self.get_binfo()
2042 self.store_info(binfo)
2050 def is_up_to_date(self, node=None, bi=None):
2051 """Returns if the node is up-to-date with respect to stored
2052 BuildInfo. The default is to compare it against our own
2053 previously stored BuildInfo, but the stored BuildInfo from another
2054 Node (typically one in a Repository) can be used instead."""
2058 bi = node.get_stored_info()
2059 new = self.get_binfo()
2062 def current(self, calc=None):
2063 self.binfo = self.gen_binfo(calc)
2067 if self.always_build:
2069 if not self.exists():
2070 # The file doesn't exist locally...
2073 # ...but there is one in a Repository...
2074 if self.is_up_to_date(r):
2075 # ...and it's even up-to-date...
2077 # ...and they'd like a local copy.
2078 LocalCopy(self, r, None)
2079 self.store_info(self.get_binfo())
2083 return self.is_up_to_date()
2087 if not self.exists():
2088 norm_name = _my_normcase(self.name)
2089 for dir in self.dir.get_all_rdirs():
2090 try: node = dir.entries[norm_name]
2091 except KeyError: node = dir.file_on_disk(self.name)
2092 if node and node.exists() and \
2093 (isinstance(node, File) or isinstance(node, Entry) \
2094 or not node.is_derived()):
2099 return str(self.rfile())
2101 def cachepath(self):
2102 if not self.fs.CachePath:
2104 ninfo = self.get_binfo().ninfo
2105 if not hasattr(ninfo, 'bsig'):
2106 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
2107 elif ninfo.bsig is None:
2108 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
2109 # Add the path to the cache signature, because multiple
2110 # targets built by the same action will all have the same
2111 # build signature, and we have to differentiate them somehow.
2112 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
2113 subdir = string.upper(cache_sig[0])
2114 dir = os.path.join(self.fs.CachePath, subdir)
2115 return dir, os.path.join(dir, cache_sig)
2117 def must_be_a_Dir(self):
2118 """Called to make sure a Node is a Dir. Since we're already a
2119 File, this is a TypeError..."""
2120 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
2124 def find_file(filename, paths, verbose=None):
2126 find_file(str, [Dir()]) -> [nodes]
2128 filename - a filename to find
2129 paths - a list of directory path *nodes* to search in. Can be
2130 represented as a list, a tuple, or a callable that is
2131 called with no arguments and returns the list or tuple.
2133 returns - the node created from the found file.
2135 Find a node corresponding to either a derived file or a file
2136 that exists already.
2138 Only the first file found is returned, and none is returned
2139 if no file is found.
2143 if not SCons.Util.is_String(verbose):
2144 verbose = "find_file"
2145 if not callable(verbose):
2146 verbose = ' %s: ' % verbose
2147 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2149 verbose = lambda x: x
2154 # Give Entries a chance to morph into Dirs.
2155 paths = map(lambda p: p.must_be_a_Dir(), paths)
2157 filedir, filename = os.path.split(filename)
2159 def filedir_lookup(p, fd=filedir):
2163 # We tried to look up a Dir, but it seems there's already
2164 # a File (or something else) there. No big.
2166 paths = filter(None, map(filedir_lookup, paths))
2169 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2170 node, d = dir.srcdir_find_file(filename)
2172 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2176 def find_files(filenames, paths):
2178 find_files([str], [Dir()]) -> [nodes]
2180 filenames - a list of filenames to find
2181 paths - a list of directory path *nodes* to search in
2183 returns - the nodes created from the found files.
2185 Finds nodes corresponding to either derived files or files
2188 Only the first file found is returned for each filename,
2189 and any files that aren't found are ignored.
2191 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2192 return filter(None, nodes)