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 from SCons.Debug import Trace
58 # The max_drift value: by default, use a cached signature value for
59 # any file that's been untouched for more than two days.
60 default_max_drift = 2*24*60*60
63 # We stringify these file system Nodes a lot. Turning a file system Node
64 # into a string is non-trivial, because the final string representation
65 # can depend on a lot of factors: whether it's a derived target or not,
66 # whether it's linked to a repository or source directory, and whether
67 # there's duplication going on. The normal technique for optimizing
68 # calculations like this is to memoize (cache) the string value, so you
69 # only have to do the calculation once.
71 # A number of the above factors, however, can be set after we've already
72 # been asked to return a string for a Node, because a Repository() or
73 # BuildDir() call or the like may not occur until later in SConscript
74 # files. So this variable controls whether we bother trying to save
75 # string values for Nodes. The wrapper interface can set this whenever
76 # they're done mucking with Repository and BuildDir and the other stuff,
77 # to let this module know it can start returning saved string values
82 def save_strings(val):
87 # SCons.Action objects for interacting with the outside world.
89 # The Node.FS methods in this module should use these actions to
90 # create and/or remove files and directories; they should *not* use
91 # os.{link,symlink,unlink,mkdir}(), etc., directly.
93 # Using these SCons.Action objects ensures that descriptions of these
94 # external activities are properly displayed, that the displays are
95 # suppressed when the -s (silent) option is used, and (most importantly)
96 # the actions are disabled when the the -n option is used, in which case
97 # there should be *no* changes to the external file system(s)...
100 def _copy_func(src, dest):
101 shutil.copy2(src, dest)
103 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
105 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
106 'hard-copy', 'soft-copy', 'copy']
108 Link_Funcs = [] # contains the callables of the specified duplication style
110 def set_duplicate(duplicate):
111 # Fill in the Link_Funcs list according to the argument
112 # (discarding those not available on the platform).
114 # Set up the dictionary that maps the argument names to the
115 # underlying implementations. We do this inside this function,
116 # not in the top-level module code, so that we can remap os.link
117 # and os.symlink for testing purposes.
119 _hardlink_func = os.link
120 except AttributeError:
121 _hardlink_func = None
124 _softlink_func = os.symlink
125 except AttributeError:
126 _softlink_func = None
129 'hard' : _hardlink_func,
130 'soft' : _softlink_func,
134 if not duplicate in Valid_Duplicates:
135 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
136 "should be in Valid_Duplicates")
139 for func in string.split(duplicate,'-'):
141 Link_Funcs.append(link_dict[func])
143 def LinkFunc(target, source, env):
144 # Relative paths cause problems with symbolic links, so
145 # we use absolute paths, which may be a problem for people
146 # who want to move their soft-linked src-trees around. Those
147 # people should use the 'hard-copy' mode, softlinks cannot be
148 # used for that; at least I have no idea how ...
149 src = source[0].abspath
150 dest = target[0].abspath
151 dir, file = os.path.split(dest)
152 if dir and not target[0].fs.isdir(dir):
155 # Set a default order of link functions.
156 set_duplicate('hard-soft-copy')
157 # Now link the files with the previously specified order.
158 for func in Link_Funcs:
162 except (IOError, OSError):
163 # An OSError indicates something happened like a permissions
164 # problem or an attempt to symlink across file-system
165 # boundaries. An IOError indicates something like the file
166 # not existing. In either case, keeping trying additional
167 # functions in the list and only raise an error if the last
169 if func == Link_Funcs[-1]:
170 # exception of the last link method (copy) are fatal
176 Link = SCons.Action.Action(LinkFunc, None)
177 def LocalString(target, source, env):
178 return 'Local copy of %s from %s' % (target[0], source[0])
180 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
182 def UnlinkFunc(target, source, env):
184 t.fs.unlink(t.abspath)
187 Unlink = SCons.Action.Action(UnlinkFunc, None)
189 def MkdirFunc(target, source, env):
192 t.fs.mkdir(t.abspath)
195 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
199 def get_MkdirBuilder():
201 if MkdirBuilder is None:
203 import SCons.Defaults
204 # "env" will get filled in by Executor.get_build_env()
205 # calling SCons.Defaults.DefaultEnvironment() when necessary.
206 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
210 target_scanner = SCons.Defaults.DirEntryScanner,
211 name = "MkdirBuilder")
214 def CacheRetrieveFunc(target, source, env):
217 cachedir, cachefile = t.cachepath()
218 if fs.exists(cachefile):
219 if SCons.Action.execute_actions:
220 fs.copy2(cachefile, t.path)
221 st = fs.stat(cachefile)
222 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
226 def CacheRetrieveString(target, source, env):
228 cachedir, cachefile = t.cachepath()
229 if t.fs.exists(cachefile):
230 return "Retrieved `%s' from cache" % t.path
233 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
235 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
237 def CachePushFunc(target, source, env):
240 cachedir, cachefile = t.cachepath()
241 if fs.exists(cachefile):
242 # Don't bother copying it if it's already there.
245 if not fs.isdir(cachedir):
246 fs.makedirs(cachedir)
248 tempfile = cachefile+'.tmp'
250 fs.copy2(t.path, tempfile)
251 fs.rename(tempfile, cachefile)
253 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
254 except (IOError, OSError):
255 # It's possible someone else tried writing the file at the
256 # same time we did, or else that there was some problem like
257 # the CacheDir being on a separate file system that's full.
258 # In any case, inability to push a file to cache doesn't affect
259 # the correctness of the build, so just print a warning.
260 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
261 "Unable to copy %s to cache. Cache file is %s"
262 % (str(target), cachefile))
265 CachePush = SCons.Action.Action(CachePushFunc, None)
272 DefaultSCCSBuilder = None
273 DefaultRCSBuilder = None
275 def get_DefaultSCCSBuilder():
276 global DefaultSCCSBuilder
277 if DefaultSCCSBuilder is None:
279 # "env" will get filled in by Executor.get_build_env()
280 # calling SCons.Defaults.DefaultEnvironment() when necessary.
281 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
282 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
284 name = "DefaultSCCSBuilder")
285 return DefaultSCCSBuilder
287 def get_DefaultRCSBuilder():
288 global DefaultRCSBuilder
289 if DefaultRCSBuilder is None:
291 # "env" will get filled in by Executor.get_build_env()
292 # calling SCons.Defaults.DefaultEnvironment() when necessary.
293 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
294 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
296 name = "DefaultRCSBuilder")
297 return DefaultRCSBuilder
299 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
300 _is_cygwin = sys.platform == "cygwin"
301 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
306 return string.upper(x)
311 def __init__(self, type, do, ignore):
317 self.__call__ = self.do
318 def set_ignore(self):
319 self.__call__ = self.ignore
321 if self.type in list:
326 def do_diskcheck_match(node, predicate, errorfmt):
329 raise TypeError, errorfmt % path
331 def ignore_diskcheck_match(node, predicate, errorfmt):
334 def do_diskcheck_rcs(node, name):
336 rcs_dir = node.rcs_dir
337 except AttributeError:
338 if node.entry_exists_on_disk('RCS'):
339 rcs_dir = node.Dir('RCS')
342 node.rcs_dir = rcs_dir
344 return rcs_dir.entry_exists_on_disk(name+',v')
347 def ignore_diskcheck_rcs(node, name):
350 def do_diskcheck_sccs(node, name):
352 sccs_dir = node.sccs_dir
353 except AttributeError:
354 if node.entry_exists_on_disk('SCCS'):
355 sccs_dir = node.Dir('SCCS')
358 node.sccs_dir = sccs_dir
360 return sccs_dir.entry_exists_on_disk('s.'+name)
363 def ignore_diskcheck_sccs(node, name):
366 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
367 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
368 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
376 def set_diskcheck(list):
377 for dc in diskcheckers:
380 def diskcheck_types():
381 return map(lambda dc: dc.type, diskcheckers)
385 class EntryProxy(SCons.Util.Proxy):
386 def __get_abspath(self):
388 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
389 entry.name + "_abspath")
391 def __get_filebase(self):
392 name = self.get().name
393 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
396 def __get_suffix(self):
397 name = self.get().name
398 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
401 def __get_file(self):
402 name = self.get().name
403 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
405 def __get_base_path(self):
406 """Return the file's directory and file name, with the
409 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
410 entry.name + "_base")
412 def __get_posix_path(self):
413 """Return the path with / as the path separator,
414 regardless of platform."""
419 r = string.replace(entry.get_path(), os.sep, '/')
420 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
422 def __get_windows_path(self):
423 """Return the path with \ as the path separator,
424 regardless of platform."""
429 r = string.replace(entry.get_path(), os.sep, '\\')
430 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
432 def __get_srcnode(self):
433 return EntryProxy(self.get().srcnode())
435 def __get_srcdir(self):
436 """Returns the directory containing the source node linked to this
437 node via BuildDir(), or the directory of this node if not linked."""
438 return EntryProxy(self.get().srcnode().dir)
440 def __get_rsrcnode(self):
441 return EntryProxy(self.get().srcnode().rfile())
443 def __get_rsrcdir(self):
444 """Returns the directory containing the source node linked to this
445 node via BuildDir(), or the directory of this node if not linked."""
446 return EntryProxy(self.get().srcnode().rfile().dir)
449 return EntryProxy(self.get().dir)
451 dictSpecialAttrs = { "base" : __get_base_path,
452 "posix" : __get_posix_path,
453 "windows" : __get_windows_path,
454 "win32" : __get_windows_path,
455 "srcpath" : __get_srcnode,
456 "srcdir" : __get_srcdir,
458 "abspath" : __get_abspath,
459 "filebase" : __get_filebase,
460 "suffix" : __get_suffix,
462 "rsrcpath" : __get_rsrcnode,
463 "rsrcdir" : __get_rsrcdir,
466 def __getattr__(self, name):
467 # This is how we implement the "special" attributes
468 # such as base, posix, srcdir, etc.
470 attr_function = self.dictSpecialAttrs[name]
473 attr = SCons.Util.Proxy.__getattr__(self, name)
474 except AttributeError:
476 classname = string.split(str(entry.__class__), '.')[-1]
477 if classname[-2:] == "'>":
478 # new-style classes report their name as:
479 # "<class 'something'>"
480 # instead of the classic classes:
482 classname = classname[:-2]
483 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
486 return attr_function(self)
488 class Base(SCons.Node.Node):
489 """A generic class for file system entries. This class is for
490 when we don't know yet whether the entry being looked up is a file
491 or a directory. Instances of this class can morph into either
492 Dir or File objects by a later, more precise lookup.
494 Note: this class does not define __cmp__ and __hash__ for
495 efficiency reasons. SCons does a lot of comparing of
496 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
497 as fast as possible, which means we want to use Python's built-in
498 object identity comparisons.
501 def __init__(self, name, directory, fs):
502 """Initialize a generic Node.FS.Base object.
504 Call the superclass initialization, take care of setting up
505 our relative and absolute paths, identify our parent
506 directory, and indicate that this node should use
508 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
509 SCons.Node.Node.__init__(self)
514 assert directory, "A directory must be provided"
516 self.abspath = directory.entry_abspath(name)
517 if directory.path == '.':
520 self.path = directory.entry_path(name)
521 if directory.tpath == '.':
524 self.tpath = directory.entry_tpath(name)
525 self.path_elements = directory.path_elements + [self]
528 self.cwd = None # will hold the SConscript directory for target nodes
529 self.duplicate = directory.duplicate
532 """Completely clear a Node.FS.Base object of all its cached
533 state (so that it can be re-evaluated by interfaces that do
534 continuous integration builds).
537 SCons.Node.Node.clear(self)
542 def get_suffix(self):
544 return SCons.Util.splitext(self.name)[1]
550 """A Node.FS.Base object's string representation is its path
554 return self._save_str()
555 return self._get_str()
559 return self._get_str()
562 if self.duplicate or self.is_derived():
563 return self.get_path()
564 return self.srcnode().get_path()
570 try: return self.fs.stat(self.abspath)
571 except os.error: return None
575 return not self.stat() is None
579 return self.rfile().exists()
583 if st: return st[stat.ST_MTIME]
588 if st: return st[stat.ST_SIZE]
593 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
597 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
599 if hasattr(os, 'symlink'):
601 try: st = self.fs.lstat(self.abspath)
602 except os.error: return 0
603 return stat.S_ISLNK(st[stat.ST_MODE])
606 return 0 # no symlinks
608 def is_under(self, dir):
612 return self.dir.is_under(dir)
618 """If this node is in a build path, return the node
619 corresponding to its source file. Otherwise, return
626 srcnode = self.fs.Entry(name, dir.srcdir,
627 klass=self.__class__)
629 name = dir.name + os.sep + name
633 def get_path(self, dir=None):
634 """Return path relative to the current working directory of the
635 Node.FS.Base object that owns us."""
637 dir = self.fs.getcwd()
640 path_elems = self.path_elements
641 try: i = path_elems.index(dir)
642 except ValueError: pass
643 else: path_elems = path_elems[i+1:]
644 path_elems = map(lambda n: n.name, path_elems)
645 return string.join(path_elems, os.sep)
647 def set_src_builder(self, builder):
648 """Set the source code builder for this node."""
649 self.sbuilder = builder
650 if not self.has_builder():
651 self.builder_set(builder)
653 def src_builder(self):
654 """Fetch the source code builder for this node.
656 If there isn't one, we cache the source code builder specified
657 for the directory (which in turn will cache the value from its
658 parent directory, and so on up to the file system root).
662 except AttributeError:
663 scb = self.dir.src_builder()
667 def get_abspath(self):
668 """Get the absolute path of the file."""
671 def for_signature(self):
672 # Return just our name. Even an absolute path would not work,
673 # because that can change thanks to symlinks or remapped network
677 def get_subst_proxy(self):
680 except AttributeError:
681 ret = EntryProxy(self)
685 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
686 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
689 """This is the class for generic Node.FS entries--that is, things
690 that could be a File or a Dir, but we're just not sure yet.
691 Consequently, the methods in this class really exist just to
692 transform their associated object into the right class when the
693 time comes, and then call the same-named method in the transformed
696 def diskcheck_match(self):
699 def disambiguate(self):
704 self.__class__ = File
710 """We're a generic Entry, but the caller is actually looking for
711 a File at this point, so morph into one."""
712 self.__class__ = File
715 return File.rfile(self)
717 def scanner_key(self):
718 return self.get_suffix()
720 def get_contents(self):
721 """Fetch the contents of the entry.
723 Since this should return the real contents from the file
724 system, we check to see into what sort of subclass we should
727 self.__class__ = File
729 return self.get_contents()
733 return self.get_contents()
735 return '' # avoid errors for dangling symlinks
738 def must_be_a_Dir(self):
739 """Called to make sure a Node is a Dir. Since we're an
740 Entry, we can morph into one."""
745 # The following methods can get called before the Taskmaster has
746 # had a chance to call disambiguate() directly to see if this Entry
747 # should really be a Dir or a File. We therefore use these to call
748 # disambiguate() transparently (from our caller's point of view).
750 # Right now, this minimal set of methods has been derived by just
751 # looking at some of the methods that will obviously be called early
752 # in any of the various Taskmasters' calling sequences, and then
753 # empirically figuring out which additional methods are necessary
754 # to make various tests pass.
757 """Return if the Entry exists. Check the file system to see
758 what we should turn into first. Assume a file if there's no
760 return self.disambiguate().exists()
762 def rel_path(self, other):
763 d = self.disambiguate()
764 if d.__class__ == Entry:
765 raise "rel_path() could not disambiguate File/Dir"
766 return d.rel_path(other)
768 # This is for later so we can differentiate between Entry the class and Entry
769 # the method of the FS class.
775 if SCons.Memoize.use_memoizer:
776 __metaclass__ = SCons.Memoize.Memoized_Metaclass
778 # This class implements an abstraction layer for operations involving
779 # a local file system. Essentially, this wraps any function in
780 # the os, os.path or shutil modules that we use to actually go do
781 # anything with or to the local file system.
783 # Note that there's a very good chance we'll refactor this part of
784 # the architecture in some way as we really implement the interface(s)
785 # for remote file system Nodes. For example, the right architecture
786 # might be to have this be a subclass instead of a base class.
787 # Nevertheless, we're using this as a first step in that direction.
789 # We're not using chdir() yet because the calling subclass method
790 # needs to use os.chdir() directly to avoid recursion. Will we
791 # really need this one?
792 #def chdir(self, path):
793 # return os.chdir(path)
794 def chmod(self, path, mode):
795 return os.chmod(path, mode)
796 def copy2(self, src, dst):
797 return shutil.copy2(src, dst)
798 def exists(self, path):
799 return os.path.exists(path)
800 def getmtime(self, path):
801 return os.path.getmtime(path)
802 def getsize(self, path):
803 return os.path.getsize(path)
804 def isdir(self, path):
805 return os.path.isdir(path)
806 def isfile(self, path):
807 return os.path.isfile(path)
808 def link(self, src, dst):
809 return os.link(src, dst)
810 def lstat(self, path):
811 return os.lstat(path)
812 def listdir(self, path):
813 return os.listdir(path)
814 def makedirs(self, path):
815 return os.makedirs(path)
816 def mkdir(self, path):
817 return os.mkdir(path)
818 def rename(self, old, new):
819 return os.rename(old, new)
820 def stat(self, path):
822 def symlink(self, src, dst):
823 return os.symlink(src, dst)
824 def open(self, path):
826 def unlink(self, path):
827 return os.unlink(path)
829 if hasattr(os, 'symlink'):
830 def islink(self, path):
831 return os.path.islink(path)
833 def islink(self, path):
834 return 0 # no symlinks
836 if SCons.Memoize.use_old_memoization():
838 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
839 def __init__(self, *args, **kw):
840 apply(_FSBase.__init__, (self,)+args, kw)
841 SCons.Memoize.Memoizer.__init__(self)
845 # # Skeleton for the obvious methods we might need from the
846 # # abstraction layer for a remote filesystem.
847 # def upload(self, local_src, remote_dst):
849 # def download(self, remote_src, local_dst):
855 def __init__(self, path = None):
856 """Initialize the Node.FS subsystem.
858 The supplied path is the top of the source tree, where we
859 expect to find the top-level build file. If no path is
860 supplied, the current directory is the default.
862 The path argument must be a valid absolute path.
864 if __debug__: logInstanceCreation(self, 'Node.FS')
867 self.SConstruct_dir = None
868 self.CachePath = None
869 self.cache_force = None
870 self.cache_show = None
871 self.max_drift = default_max_drift
875 self.pathTop = os.getcwd()
878 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
880 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
885 def clear_cache(self):
889 def set_SConstruct_dir(self, dir):
890 self.SConstruct_dir = dir
892 def get_max_drift(self):
893 return self.max_drift
895 def set_max_drift(self, max_drift):
896 self.max_drift = max_drift
901 def __checkClass(self, node, klass):
902 if isinstance(node, klass) or klass == Entry:
904 if node.__class__ == Entry:
905 node.__class__ = klass
908 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
909 (node.__class__.__name__, node.path, klass.__name__)
911 def _doLookup(self, fsclass, name, directory = None, create = 1):
912 """This method differs from the File and Dir factory methods in
913 one important way: the meaning of the directory parameter.
914 In this method, if directory is None or not supplied, the supplied
915 name is expected to be an absolute path. If you try to look up a
916 relative path with directory=None, then an AssertionError will be
921 # This is a stupid hack to compensate for the fact that the
922 # POSIX and Windows versions of os.path.normpath() behave
923 # differently in older versions of Python. In particular,
925 # os.path.normpath('./') == '.'
927 # os.path.normpath('./') == ''
928 # os.path.normpath('.\\') == ''
930 # This is a definite bug in the Python library, but we have
933 path_orig = string.split(name, os.sep)
934 path_norm = string.split(_my_normcase(name), os.sep)
936 first_orig = path_orig.pop(0) # strip first element
937 first_norm = path_norm.pop(0) # strip first element
939 drive, path_first = os.path.splitdrive(first_orig)
941 path_orig = [ path_first, ] + path_orig
942 path_norm = [ _my_normcase(path_first), ] + path_norm
945 drive = _my_normcase(drive)
947 directory = self.Root[drive]
950 raise SCons.Errors.UserError
951 directory = RootDir(drive, self)
952 self.Root[drive] = directory
954 self.Root[self.defaultDrive] = directory
955 elif drive == self.defaultDrive:
956 self.Root[''] = directory
961 last_orig = path_orig.pop() # strip last element
962 last_norm = path_norm.pop() # strip last element
964 # Lookup the directory
965 for orig, norm in map(None, path_orig, path_norm):
967 entries = directory.entries
968 except AttributeError:
969 # We tried to look up the entry in either an Entry or
970 # a File. Give whatever it is a chance to do what's
971 # appropriate: morph into a Dir or raise an exception.
972 directory.must_be_a_Dir()
973 entries = directory.entries
975 directory = entries[norm]
978 raise SCons.Errors.UserError
980 d = Dir(orig, directory, self)
982 # Check the file system (or not, as configured) to make
983 # sure there isn't already a file there.
986 directory.entries[norm] = d
987 directory.add_wkid(d)
990 directory.must_be_a_Dir()
993 e = directory.entries[last_norm]
996 raise SCons.Errors.UserError
998 result = fsclass(last_orig, directory, self)
1000 # Check the file system (or not, as configured) to make
1001 # sure there isn't already a directory at the path on
1002 # disk where we just created a File node, and vice versa.
1003 result.diskcheck_match()
1005 directory.entries[last_norm] = result
1006 directory.add_wkid(result)
1008 result = self.__checkClass(e, fsclass)
1011 def _transformPath(self, name, directory):
1012 """Take care of setting up the correct top-level directory,
1013 usually in preparation for a call to doLookup().
1015 If the path name is prepended with a '#', then it is unconditionally
1016 interpreted as relative to the top-level directory of this FS.
1018 If directory is None, and name is a relative path,
1019 then the same applies.
1021 if not SCons.Util.is_String(name):
1022 # This handles cases where the object is a Proxy wrapping
1023 # a Node.FS.File object (e.g.). It would be good to handle
1024 # this more directly some day by having the callers of this
1025 # function recognize that a Proxy can be treated like the
1026 # underlying object (that is, get rid of the isinstance()
1027 # calls that explicitly look for a Node.FS.Base object).
1029 if name and name[0] == '#':
1030 directory = self.Top
1032 if name and (name[0] == os.sep or name[0] == '/'):
1033 # Correct such that '#/foo' is equivalent
1036 name = os.path.join('.', os.path.normpath(name))
1038 directory = self._cwd
1039 return (os.path.normpath(name), directory)
1041 def chdir(self, dir, change_os_dir=0):
1042 """Change the current working directory for lookups.
1043 If change_os_dir is true, we will also change the "real" cwd
1051 os.chdir(dir.abspath)
1056 def Entry(self, name, directory = None, create = 1, klass=None):
1057 """Lookup or create a generic Entry node with the specified name.
1058 If the name is a relative path (begins with ./, ../, or a file
1059 name), then it is looked up relative to the supplied directory
1060 node, or to the top level directory of the FS (supplied at
1061 construction time) if no directory is supplied.
1067 if isinstance(name, Base):
1068 return self.__checkClass(name, klass)
1070 if directory and not isinstance(directory, Dir):
1071 directory = self.Dir(directory)
1072 name, directory = self._transformPath(name, directory)
1073 return self._doLookup(klass, name, directory, create)
1075 def File(self, name, directory = None, create = 1):
1076 """Lookup or create a File node with the specified name. If
1077 the name is a relative path (begins with ./, ../, or a file name),
1078 then it is looked up relative to the supplied directory node,
1079 or to the top level directory of the FS (supplied at construction
1080 time) if no directory is supplied.
1082 This method will raise TypeError if a directory is found at the
1086 return self.Entry(name, directory, create, File)
1088 def Dir(self, name, directory = None, create = 1):
1089 """Lookup or create a Dir node with the specified name. If
1090 the name is a relative path (begins with ./, ../, or a file name),
1091 then it is looked up relative to the supplied directory node,
1092 or to the top level directory of the FS (supplied at construction
1093 time) if no directory is supplied.
1095 This method will raise TypeError if a normal file is found at the
1099 return self.Entry(name, directory, create, Dir)
1101 def BuildDir(self, build_dir, src_dir, duplicate=1):
1102 """Link the supplied build directory to the source directory
1103 for purposes of building files."""
1105 if not isinstance(src_dir, SCons.Node.Node):
1106 src_dir = self.Dir(src_dir)
1107 if not isinstance(build_dir, SCons.Node.Node):
1108 build_dir = self.Dir(build_dir)
1109 if src_dir.is_under(build_dir):
1110 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1111 if build_dir.srcdir:
1112 if build_dir.srcdir == src_dir:
1113 return # We already did this.
1114 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1115 build_dir.link(src_dir, duplicate)
1117 def Repository(self, *dirs):
1118 """Specify Repository directories to search."""
1120 if not isinstance(d, SCons.Node.Node):
1122 self.Top.addRepository(d)
1124 def Rfindalldirs(self, pathlist, cwd):
1126 if SCons.Util.is_String(pathlist):
1127 pathlist = string.split(pathlist, os.pathsep)
1128 if not SCons.Util.is_List(pathlist):
1129 pathlist = [pathlist]
1131 for path in filter(None, pathlist):
1132 if isinstance(path, SCons.Node.Node):
1135 path, dir = self._transformPath(path, cwd)
1137 result.extend(dir.get_all_rdirs())
1140 def CacheDir(self, path):
1141 self.CachePath = path
1143 def build_dir_target_climb(self, orig, dir, tail):
1144 """Create targets in corresponding build directories
1146 Climb the directory tree, and look up path names
1147 relative to any linked build directories we find.
1152 fmt = "building associated BuildDir targets: %s"
1155 for bd in dir.build_dirs:
1156 if start_dir.is_under(bd):
1157 # If already in the build-dir location, don't reflect
1158 return [orig], fmt % str(orig)
1159 p = apply(os.path.join, [bd.path] + tail)
1160 targets.append(self.Entry(p))
1161 tail = [dir.name] + tail
1164 message = fmt % string.join(map(str, targets))
1165 return targets, message
1167 class DirNodeInfo(SCons.Node.NodeInfoBase):
1170 class DirBuildInfo(SCons.Node.BuildInfoBase):
1174 """A class for directories in a file system.
1177 NodeInfo = DirNodeInfo
1178 BuildInfo = DirBuildInfo
1180 def __init__(self, name, directory, fs):
1181 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1182 Base.__init__(self, name, directory, fs)
1186 """Turn a file system Node (either a freshly initialized directory
1187 object or a separate Entry object) into a proper directory object.
1189 Set up this directory's entries and hook it into the file
1190 system tree. Specify that directories (this Node) don't use
1191 signatures for calculating whether they're current.
1194 self.repositories = []
1198 self.entries['.'] = self
1199 self.entries['..'] = self.dir
1202 self._sconsign = None
1203 self.build_dirs = []
1205 # Don't just reset the executor, replace its action list,
1206 # because it might have some pre-or post-actions that need to
1208 self.builder = get_MkdirBuilder()
1209 self.get_executor().set_action_list(self.builder.action)
1211 def diskcheck_match(self):
1212 diskcheck_match(self, self.fs.isfile,
1213 "File %s found where directory expected.")
1215 def __clearRepositoryCache(self, duplicate=None):
1216 """Called when we change the repository(ies) for a directory.
1217 This clears any cached information that is invalidated by changing
1220 for node in self.entries.values():
1221 if node != self.dir:
1222 if node != self and isinstance(node, Dir):
1223 node.__clearRepositoryCache(duplicate)
1228 except AttributeError:
1230 if duplicate != None:
1231 node.duplicate=duplicate
1233 def __resetDuplicate(self, node):
1235 node.duplicate = node.get_dir().duplicate
1237 def Entry(self, name):
1238 """Create an entry node named 'name' relative to this directory."""
1239 return self.fs.Entry(name, self)
1241 def Dir(self, name):
1242 """Create a directory node named 'name' relative to this directory."""
1243 return self.fs.Dir(name, self)
1245 def File(self, name):
1246 """Create a file node named 'name' relative to this directory."""
1247 return self.fs.File(name, self)
1249 def link(self, srcdir, duplicate):
1250 """Set this directory as the build directory for the
1251 supplied source directory."""
1252 self.srcdir = srcdir
1253 self.duplicate = duplicate
1254 self.__clearRepositoryCache(duplicate)
1255 srcdir.build_dirs.append(self)
1257 def getRepositories(self):
1258 """Returns a list of repositories for this directory.
1260 if self.srcdir and not self.duplicate:
1261 return self.srcdir.get_all_rdirs() + self.repositories
1262 return self.repositories
1264 def get_all_rdirs(self):
1270 for rep in dir.getRepositories():
1271 result.append(rep.Dir(fname))
1272 fname = dir.name + os.sep + fname
1276 def addRepository(self, dir):
1277 if dir != self and not dir in self.repositories:
1278 self.repositories.append(dir)
1280 self.__clearRepositoryCache()
1283 return self.entries['..']
1285 def rel_path(self, other):
1286 """Return a path to "other" relative to this directory.
1288 if isinstance(other, Dir):
1294 except AttributeError:
1297 return name and name[0] or '.'
1299 for x, y in map(None, self.path_elements, other.path_elements):
1303 path_elems = ['..']*(len(self.path_elements)-i) \
1304 + map(lambda n: n.name, other.path_elements[i:]) \
1307 return string.join(path_elems, os.sep)
1309 def get_env_scanner(self, env, kw={}):
1310 return SCons.Defaults.DirEntryScanner
1312 def get_target_scanner(self):
1313 return SCons.Defaults.DirEntryScanner
1315 def get_found_includes(self, env, scanner, path):
1316 """Return the included implicit dependencies in this file.
1317 Cache results so we only scan the file once per path
1318 regardless of how many times this information is requested.
1322 # Clear cached info for this Dir. If we already visited this
1323 # directory on our walk down the tree (because we didn't know at
1324 # that point it was being used as the source for another Node)
1325 # then we may have calculated build signature before realizing
1326 # we had to scan the disk. Now that we have to, though, we need
1327 # to invalidate the old calculated signature so that any node
1328 # dependent on our directory structure gets one that includes
1329 # info about everything on disk.
1331 return scanner(self, env, path)
1333 def build(self, **kw):
1334 """A null "builder" for directories."""
1336 if not self.builder is MkdirBuilder:
1337 apply(SCons.Node.Node.build, [self,], kw)
1340 """Create this directory, silently and without worrying about
1341 whether the builder is the default or not."""
1347 listDirs.append(parent)
1350 raise SCons.Errors.StopError, parent.path
1353 for dirnode in listDirs:
1355 # Don't call dirnode.build(), call the base Node method
1356 # directly because we definitely *must* create this
1357 # directory. The dirnode.build() method will suppress
1358 # the build if it's the default builder.
1359 SCons.Node.Node.build(dirnode)
1360 dirnode.get_executor().nullify()
1361 # The build() action may or may not have actually
1362 # created the directory, depending on whether the -n
1363 # option was used or not. Delete the _exists and
1364 # _rexists attributes so they can be reevaluated.
1369 def multiple_side_effect_has_builder(self):
1371 return not self.builder is MkdirBuilder and self.has_builder()
1373 def alter_targets(self):
1374 """Return any corresponding targets in a build directory.
1376 return self.fs.build_dir_target_climb(self, self, [])
1378 def scanner_key(self):
1379 """A directory does not get scanned."""
1382 def get_contents(self):
1383 """Return aggregate contents of all our children."""
1384 contents = map(lambda n: n.get_contents(), self.children())
1385 return string.join(contents, '')
1390 def do_duplicate(self, src):
1393 def current(self, calc=None):
1394 """If any child is not up-to-date, then this directory isn't,
1396 if not self.builder is MkdirBuilder and not self.exists():
1398 up_to_date = SCons.Node.up_to_date
1399 for kid in self.children():
1400 if kid.get_state() > up_to_date:
1406 if not self.exists():
1407 norm_name = _my_normcase(self.name)
1408 for dir in self.dir.get_all_rdirs():
1409 try: node = dir.entries[norm_name]
1410 except KeyError: node = dir.dir_on_disk(self.name)
1411 if node and node.exists() and \
1412 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1417 """Return the .sconsign file info for this directory,
1418 creating it first if necessary."""
1419 if not self._sconsign:
1420 import SCons.SConsign
1421 self._sconsign = SCons.SConsign.ForDirectory(self)
1422 return self._sconsign
1425 """Dir has a special need for srcnode()...if we
1426 have a srcdir attribute set, then that *is* our srcnode."""
1429 return Base.srcnode(self)
1431 def get_timestamp(self):
1432 """Return the latest timestamp from among our children"""
1434 for kid in self.children():
1435 if kid.get_timestamp() > stamp:
1436 stamp = kid.get_timestamp()
1439 def entry_abspath(self, name):
1440 return self.abspath + os.sep + name
1442 def entry_path(self, name):
1443 return self.path + os.sep + name
1445 def entry_tpath(self, name):
1446 return self.tpath + os.sep + name
1448 def must_be_a_Dir(self):
1449 """Called to make sure a Node is a Dir. Since we're already
1450 one, this is a no-op for us."""
1453 def entry_exists_on_disk(self, name):
1456 d = self.on_disk_entries
1457 except AttributeError:
1460 entries = os.listdir(self.abspath)
1464 for entry in map(_my_normcase, entries):
1466 self.on_disk_entries = d
1467 return d.has_key(_my_normcase(name))
1469 def srcdir_list(self):
1477 d = dir.srcdir.Dir(dirname)
1479 # Shouldn't source from something in the build path:
1480 # build_dir is probably under src_dir, in which case
1481 # we are reflecting.
1484 dirname = dir.name + os.sep + dirname
1489 def srcdir_duplicate(self, name):
1490 for dir in self.srcdir_list():
1491 if dir.entry_exists_on_disk(name):
1492 srcnode = dir.File(name)
1494 node = self.File(name)
1495 node.do_duplicate(srcnode)
1501 def srcdir_find_file(self, filename):
1504 if (isinstance(node, File) or isinstance(node, Entry)) and \
1505 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1509 norm_name = _my_normcase(filename)
1511 for rdir in self.get_all_rdirs():
1512 try: node = rdir.entries[norm_name]
1513 except KeyError: node = rdir.file_on_disk(filename)
1514 else: node = func(node)
1518 for srcdir in self.srcdir_list():
1519 for rdir in srcdir.get_all_rdirs():
1520 try: node = rdir.entries[norm_name]
1521 except KeyError: node = rdir.file_on_disk(filename)
1522 else: node = func(node)
1524 return File(filename, self, self.fs), srcdir
1528 def dir_on_disk(self, name):
1529 if self.entry_exists_on_disk(name):
1530 try: return self.Dir(name)
1531 except TypeError: pass
1534 def file_on_disk(self, name):
1535 if self.entry_exists_on_disk(name) or \
1536 diskcheck_rcs(self, name) or \
1537 diskcheck_sccs(self, name):
1538 try: return self.File(name)
1539 except TypeError: pass
1540 return self.srcdir_duplicate(name)
1543 """A class for the root directory of a file system.
1545 This is the same as a Dir class, except that the path separator
1546 ('/' or '\\') is actually part of the name, so we don't need to
1547 add a separator when creating the path names of entries within
1550 def __init__(self, name, fs):
1551 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1552 # We're going to be our own parent directory (".." entry and .dir
1553 # attribute) so we have to set up some values so Base.__init__()
1554 # won't gag won't it calls some of our methods.
1558 self.path_elements = []
1560 Base.__init__(self, name, self, fs)
1562 # Now set our paths to what we really want them to be: the
1563 # initial drive letter (the name) plus the directory separator.
1564 self.abspath = name + os.sep
1565 self.path = name + os.sep
1566 self.tpath = name + os.sep
1572 def entry_abspath(self, name):
1573 return self.abspath + name
1575 def entry_path(self, name):
1576 return self.path + name
1578 def entry_tpath(self, name):
1579 return self.tpath + name
1581 def is_under(self, dir):
1593 def src_builder(self):
1596 class FileNodeInfo(SCons.Node.NodeInfoBase):
1597 def __init__(self, node):
1598 SCons.Node.NodeInfoBase.__init__(self, node)
1600 def __cmp__(self, other):
1601 try: return cmp(self.bsig, other.bsig)
1602 except AttributeError: return 1
1603 def update(self, node):
1604 self.timestamp = node.get_timestamp()
1605 self.size = node.getsize()
1607 class FileBuildInfo(SCons.Node.BuildInfoBase):
1608 def __init__(self, node):
1609 SCons.Node.BuildInfoBase.__init__(self, node)
1611 def convert_to_sconsign(self):
1612 """Convert this BuildInfo object for writing to a .sconsign file
1614 We hung onto the node that we refer to so that we can translate
1615 the lists of bsources, bdepends and bimplicit Nodes into strings
1616 relative to the node, but we don't want to write out that Node
1617 itself to the .sconsign file, so we delete the attribute in
1620 rel_path = self.node.rel_path
1621 delattr(self, 'node')
1622 for attr in ['bsources', 'bdepends', 'bimplicit']:
1624 val = getattr(self, attr)
1625 except AttributeError:
1628 setattr(self, attr, map(rel_path, val))
1629 def convert_from_sconsign(self, dir, name):
1630 """Convert a newly-read BuildInfo object for in-SCons use
1632 An on-disk BuildInfo comes without a reference to the node
1633 for which it's intended, so we have to convert the arguments
1634 and add back a self.node attribute. The bsources, bdepends and
1635 bimplicit lists all come from disk as paths relative to that node,
1636 so convert them to actual Nodes for use by the rest of SCons.
1638 self.node = dir.Entry(name)
1639 Entry_func = self.node.dir.Entry
1640 for attr in ['bsources', 'bdepends', 'bimplicit']:
1642 val = getattr(self, attr)
1643 except AttributeError:
1646 setattr(self, attr, map(Entry_func, val))
1648 result = [ self.ninfo.format() ]
1649 bkids = self.bsources + self.bdepends + self.bimplicit
1650 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
1651 for i in xrange(len(bkids)):
1652 result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
1653 return string.join(result, '\n')
1655 class NodeInfo(FileNodeInfo):
1658 class BuildInfo(FileBuildInfo):
1662 """A class for files in a file system.
1665 NodeInfo = FileNodeInfo
1666 BuildInfo = FileBuildInfo
1668 def diskcheck_match(self):
1669 diskcheck_match(self, self.fs.isdir,
1670 "Directory %s found where file expected.")
1672 def __init__(self, name, directory, fs):
1673 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1674 Base.__init__(self, name, directory, fs)
1677 def Entry(self, name):
1678 """Create an entry node named 'name' relative to
1679 the SConscript directory of this file."""
1680 return self.fs.Entry(name, self.cwd)
1682 def Dir(self, name):
1683 """Create a directory node named 'name' relative to
1684 the SConscript directory of this file."""
1685 return self.fs.Dir(name, self.cwd)
1687 def Dirs(self, pathlist):
1688 """Create a list of directories relative to the SConscript
1689 directory of this file."""
1690 return map(lambda p, s=self: s.Dir(p), pathlist)
1692 def File(self, name):
1693 """Create a file node named 'name' relative to
1694 the SConscript directory of this file."""
1695 return self.fs.File(name, self.cwd)
1697 def RDirs(self, pathlist):
1698 """Search for a list of directories in the Repository list."""
1699 return self.fs.Rfindalldirs(pathlist, self.cwd)
1701 #def generate_build_dict(self):
1702 # """Return an appropriate dictionary of values for building
1704 # return {'Dir' : self.Dir,
1705 # 'File' : self.File,
1706 # 'RDirs' : self.RDirs}
1709 """Turn a file system node into a File object. __cache_reset__"""
1710 self.scanner_paths = {}
1711 if not hasattr(self, '_local'):
1714 def scanner_key(self):
1715 return self.get_suffix()
1717 def get_contents(self):
1718 if not self.rexists():
1720 return open(self.rfile().abspath, "rb").read()
1722 def get_timestamp(self):
1724 return self.rfile().getmtime()
1728 def store_info(self, obj):
1729 # Merge our build information into the already-stored entry.
1730 # This accomodates "chained builds" where a file that's a target
1731 # in one build (SConstruct file) is a source in a different build.
1732 # See test/chained-build.py for the use case.
1733 entry = self.get_stored_info()
1735 self.dir.sconsign().set_entry(self.name, entry)
1737 def get_stored_info(self):
1740 stored = self.dir.sconsign().get_entry(self.name)
1741 except (KeyError, OSError):
1742 return self.new_binfo()
1744 if not hasattr(stored, 'ninfo'):
1745 # Transition: The .sconsign file entry has no NodeInfo
1746 # object, which means it's a slightly older BuildInfo.
1747 # Copy over the relevant attributes.
1748 ninfo = stored.ninfo = self.new_ninfo()
1749 for attr in ninfo.__dict__.keys():
1751 setattr(ninfo, attr, getattr(stored, attr))
1752 except AttributeError:
1756 def get_stored_implicit(self):
1757 binfo = self.get_stored_info()
1758 try: return binfo.bimplicit
1759 except AttributeError: return None
1761 def rel_path(self, other):
1762 return self.dir.rel_path(other)
1764 def get_found_includes(self, env, scanner, path):
1765 """Return the included implicit dependencies in this file.
1766 Cache results so we only scan the file once per path
1767 regardless of how many times this information is requested.
1771 return scanner(self, env, path)
1773 def _createDir(self):
1774 # ensure that the directories for this node are
1778 def retrieve_from_cache(self):
1779 """Try to retrieve the node's content from a cache
1781 This method is called from multiple threads in a parallel build,
1782 so only do thread safe stuff here. Do thread unsafe stuff in
1785 Note that there's a special trick here with the execute flag
1786 (one that's not normally done for other actions). Basically
1787 if the user requested a noexec (-n) build, then
1788 SCons.Action.execute_actions is set to 0 and when any action
1789 is called, it does its showing but then just returns zero
1790 instead of actually calling the action execution operation.
1791 The problem for caching is that if the file does NOT exist in
1792 cache then the CacheRetrieveString won't return anything to
1793 show for the task, but the Action.__call__ won't call
1794 CacheRetrieveFunc; instead it just returns zero, which makes
1795 the code below think that the file *was* successfully
1796 retrieved from the cache, therefore it doesn't do any
1797 subsequent building. However, the CacheRetrieveString didn't
1798 print anything because it didn't actually exist in the cache,
1799 and no more build actions will be performed, so the user just
1800 sees nothing. The fix is to tell Action.__call__ to always
1801 execute the CacheRetrieveFunc and then have the latter
1802 explicitly check SCons.Action.execute_actions itself.
1804 Returns true iff the node was successfully retrieved.
1806 b = self.is_derived()
1807 if not b and not self.has_src_builder():
1809 if b and self.fs.CachePath:
1810 if self.fs.cache_show:
1811 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1812 self.build(presub=0, execute=0)
1814 elif CacheRetrieve(self, [], None, execute=1) == 0:
1819 """Called just after this node is successfully built.
1821 # Push this file out to cache before the superclass Node.built()
1822 # method has a chance to clear the build signature, which it
1823 # will do if this file has a source scanner.
1824 if self.fs.CachePath and self.exists():
1825 CachePush(self, [], None)
1826 self.fs.clear_cache()
1827 SCons.Node.Node.built(self)
1830 if self.fs.CachePath and self.fs.cache_force and self.exists():
1831 CachePush(self, None, None)
1833 def has_src_builder(self):
1834 """Return whether this Node has a source builder or not.
1836 If this Node doesn't have an explicit source code builder, this
1837 is where we figure out, on the fly, if there's a transparent
1838 source code builder for it.
1840 Note that if we found a source builder, we also set the
1841 self.builder attribute, so that all of the methods that actually
1842 *build* this file don't have to do anything different.
1846 except AttributeError:
1850 scb = self.dir.src_builder()
1852 if diskcheck_sccs(self.dir, self.name):
1853 scb = get_DefaultSCCSBuilder()
1854 elif diskcheck_rcs(self.dir, self.name):
1855 scb = get_DefaultRCSBuilder()
1859 self.builder_set(scb)
1861 return not scb is None
1863 def alter_targets(self):
1864 """Return any corresponding targets in a build directory.
1866 if self.is_derived():
1868 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1870 def is_pseudo_derived(self):
1872 return self.has_src_builder()
1874 def _rmv_existing(self):
1876 Unlink(self, [], None)
1879 """Prepare for this file to be created."""
1880 SCons.Node.Node.prepare(self)
1882 if self.get_state() != SCons.Node.up_to_date:
1884 if self.is_derived() and not self.precious:
1885 self._rmv_existing()
1889 except SCons.Errors.StopError, drive:
1890 desc = "No drive `%s' for target `%s'." % (drive, self)
1891 raise SCons.Errors.StopError, desc
1894 """Remove this file."""
1895 if self.exists() or self.islink():
1896 self.fs.unlink(self.path)
1900 def do_duplicate(self, src):
1903 Unlink(self, None, None)
1904 except SCons.Errors.BuildError:
1907 Link(self, src, None)
1908 except SCons.Errors.BuildError, e:
1909 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1910 raise SCons.Errors.StopError, desc
1912 # The Link() action may or may not have actually
1913 # created the file, depending on whether the -n
1914 # option was used or not. Delete the _exists and
1915 # _rexists attributes so they can be reevaluated.
1920 # Duplicate from source path if we are set up to do this.
1921 if self.duplicate and not self.is_derived() and not self.linked:
1922 src = self.srcnode()
1924 return Base.exists(self)
1925 # At this point, src is meant to be copied in a build directory.
1927 if src.abspath != self.abspath:
1929 self.do_duplicate(src)
1930 # Can't return 1 here because the duplication might
1931 # not actually occur if the -n option is being used.
1933 # The source file does not exist. Make sure no old
1934 # copy remains in the build directory.
1935 if Base.exists(self) or self.islink():
1936 self.fs.unlink(self.path)
1937 # Return None explicitly because the Base.exists() call
1938 # above will have cached its value if the file existed.
1940 return Base.exists(self)
1943 # SIGNATURE SUBSYSTEM
1946 def get_csig(self, calc=None):
1948 Generate a node's content signature, the digested signature
1952 cache - alternate node to use for the signature cache
1953 returns - the content signature
1956 return self.binfo.ninfo.csig
1957 except AttributeError:
1961 calc = self.calculator()
1963 max_drift = self.fs.max_drift
1964 mtime = self.get_timestamp()
1965 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
1969 old = self.get_stored_info().ninfo
1971 if old.timestamp and old.csig and old.timestamp == mtime:
1973 except AttributeError:
1976 csig = calc.module.signature(self)
1978 binfo = self.get_binfo()
1984 self.store_info(binfo)
1992 def is_up_to_date(self, node=None, bi=None):
1993 """Returns if the node is up-to-date with respect to stored
1994 BuildInfo. The default is to compare it against our own
1995 previously stored BuildInfo, but the stored BuildInfo from another
1996 Node (typically one in a Repository) can be used instead."""
2000 bi = node.get_stored_info()
2001 new = self.get_binfo()
2004 def current(self, calc=None):
2005 self.binfo = self.gen_binfo(calc)
2009 if self.always_build:
2011 if not self.exists():
2012 # The file doesn't exist locally...
2015 # ...but there is one in a Repository...
2016 if self.is_up_to_date(r):
2017 # ...and it's even up-to-date...
2019 # ...and they'd like a local copy.
2020 LocalCopy(self, r, None)
2021 self.store_info(self.get_binfo())
2025 return self.is_up_to_date()
2029 if not self.exists():
2030 norm_name = _my_normcase(self.name)
2031 for dir in self.dir.get_all_rdirs():
2032 try: node = dir.entries[norm_name]
2033 except KeyError: node = dir.file_on_disk(self.name)
2034 if node and node.exists() and \
2035 (isinstance(node, File) or isinstance(node, Entry) \
2036 or not node.is_derived()):
2041 return str(self.rfile())
2043 def cachepath(self):
2044 if not self.fs.CachePath:
2046 ninfo = self.get_binfo().ninfo
2047 if not hasattr(ninfo, 'bsig'):
2048 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
2049 elif ninfo.bsig is None:
2050 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
2051 # Add the path to the cache signature, because multiple
2052 # targets built by the same action will all have the same
2053 # build signature, and we have to differentiate them somehow.
2054 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
2055 subdir = string.upper(cache_sig[0])
2056 dir = os.path.join(self.fs.CachePath, subdir)
2057 return dir, os.path.join(dir, cache_sig)
2059 def must_be_a_Dir(self):
2060 """Called to make sure a Node is a Dir. Since we're already a
2061 File, this is a TypeError..."""
2062 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
2066 def find_file(filename, paths, verbose=None):
2068 find_file(str, [Dir()]) -> [nodes]
2070 filename - a filename to find
2071 paths - a list of directory path *nodes* to search in. Can be
2072 represented as a list, a tuple, or a callable that is
2073 called with no arguments and returns the list or tuple.
2075 returns - the node created from the found file.
2077 Find a node corresponding to either a derived file or a file
2078 that exists already.
2080 Only the first file found is returned, and none is returned
2081 if no file is found.
2085 if not SCons.Util.is_String(verbose):
2086 verbose = "find_file"
2087 if not callable(verbose):
2088 verbose = ' %s: ' % verbose
2089 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2091 verbose = lambda x: x
2096 # Give Entries a chance to morph into Dirs.
2097 paths = map(lambda p: p.must_be_a_Dir(), paths)
2099 filedir, filename = os.path.split(filename)
2101 def filedir_lookup(p, fd=filedir):
2105 # We tried to look up a Dir, but it seems there's already
2106 # a File (or something else) there. No big.
2108 paths = filter(None, map(filedir_lookup, paths))
2111 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2112 node, d = dir.srcdir_find_file(filename)
2114 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2118 def find_files(filenames, paths):
2120 find_files([str], [Dir()]) -> [nodes]
2122 filenames - a list of filenames to find
2123 paths - a list of directory path *nodes* to search in
2125 returns - the nodes created from the found files.
2127 Finds nodes corresponding to either derived files or files
2130 Only the first file found is returned for each filename,
2131 and any files that aren't found are ignored.
2133 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2134 return filter(None, nodes)