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 def _copy_func(src, dest):
99 shutil.copy2(src, dest)
101 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
103 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
104 'hard-copy', 'soft-copy', 'copy']
106 Link_Funcs = [] # contains the callables of the specified duplication style
108 def set_duplicate(duplicate):
109 # Fill in the Link_Funcs list according to the argument
110 # (discarding those not available on the platform).
112 # Set up the dictionary that maps the argument names to the
113 # underlying implementations. We do this inside this function,
114 # not in the top-level module code, so that we can remap os.link
115 # and os.symlink for testing purposes.
117 _hardlink_func = os.link
118 except AttributeError:
119 _hardlink_func = None
122 _softlink_func = os.symlink
123 except AttributeError:
124 _softlink_func = None
127 'hard' : _hardlink_func,
128 'soft' : _softlink_func,
132 if not duplicate in Valid_Duplicates:
133 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
134 "should be in Valid_Duplicates")
137 for func in string.split(duplicate,'-'):
139 Link_Funcs.append(link_dict[func])
141 def LinkFunc(target, source, env):
142 # Relative paths cause problems with symbolic links, so
143 # we use absolute paths, which may be a problem for people
144 # who want to move their soft-linked src-trees around. Those
145 # people should use the 'hard-copy' mode, softlinks cannot be
146 # used for that; at least I have no idea how ...
147 src = source[0].abspath
148 dest = target[0].abspath
149 dir, file = os.path.split(dest)
150 if dir and not target[0].fs.isdir(dir):
153 # Set a default order of link functions.
154 set_duplicate('hard-soft-copy')
155 # Now link the files with the previously specified order.
156 for func in Link_Funcs:
160 except (IOError, OSError):
161 # An OSError indicates something happened like a permissions
162 # problem or an attempt to symlink across file-system
163 # boundaries. An IOError indicates something like the file
164 # not existing. In either case, keeping trying additional
165 # functions in the list and only raise an error if the last
167 if func == Link_Funcs[-1]:
168 # exception of the last link method (copy) are fatal
174 Link = SCons.Action.Action(LinkFunc, None)
175 def LocalString(target, source, env):
176 return 'Local copy of %s from %s' % (target[0], source[0])
178 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
180 def UnlinkFunc(target, source, env):
182 t.fs.unlink(t.abspath)
185 Unlink = SCons.Action.Action(UnlinkFunc, None)
187 def MkdirFunc(target, source, env):
190 t.fs.mkdir(t.abspath)
193 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
197 def get_MkdirBuilder():
199 if MkdirBuilder is None:
201 # "env" will get filled in by Executor.get_build_env()
202 # calling SCons.Defaults.DefaultEnvironment() when necessary.
203 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
207 name = "MkdirBuilder")
210 def CacheRetrieveFunc(target, source, env):
213 cachedir, cachefile = t.cachepath()
214 if fs.exists(cachefile):
215 if SCons.Action.execute_actions:
216 fs.copy2(cachefile, t.path)
217 st = fs.stat(cachefile)
218 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
222 def CacheRetrieveString(target, source, env):
224 cachedir, cachefile = t.cachepath()
225 if t.fs.exists(cachefile):
226 return "Retrieved `%s' from cache" % t.path
229 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
231 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
233 def CachePushFunc(target, source, env):
236 cachedir, cachefile = t.cachepath()
237 if fs.exists(cachefile):
238 # Don't bother copying it if it's already there.
241 if not fs.isdir(cachedir):
242 fs.makedirs(cachedir)
244 tempfile = cachefile+'.tmp'
246 fs.copy2(t.path, tempfile)
247 fs.rename(tempfile, cachefile)
249 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
250 except (IOError, OSError):
251 # It's possible someone else tried writing the file at the
252 # same time we did, or else that there was some problem like
253 # the CacheDir being on a separate file system that's full.
254 # In any case, inability to push a file to cache doesn't affect
255 # the correctness of the build, so just print a warning.
256 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
257 "Unable to copy %s to cache. Cache file is %s"
258 % (str(target), cachefile))
261 CachePush = SCons.Action.Action(CachePushFunc, None)
268 DefaultSCCSBuilder = None
269 DefaultRCSBuilder = None
271 def get_DefaultSCCSBuilder():
272 global DefaultSCCSBuilder
273 if DefaultSCCSBuilder is None:
275 # "env" will get filled in by Executor.get_build_env()
276 # calling SCons.Defaults.DefaultEnvironment() when necessary.
277 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
278 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
280 name = "DefaultSCCSBuilder")
281 return DefaultSCCSBuilder
283 def get_DefaultRCSBuilder():
284 global DefaultRCSBuilder
285 if DefaultRCSBuilder is None:
287 # "env" will get filled in by Executor.get_build_env()
288 # calling SCons.Defaults.DefaultEnvironment() when necessary.
289 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
290 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
292 name = "DefaultRCSBuilder")
293 return DefaultRCSBuilder
295 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
296 _is_cygwin = sys.platform == "cygwin"
297 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
302 return string.upper(x)
307 def __init__(self, type, do, ignore):
313 self.__call__ = self.do
314 def set_ignore(self):
315 self.__call__ = self.ignore
317 if self.type in list:
322 def do_diskcheck_match(node, predicate, errorfmt):
325 raise TypeError, errorfmt % path
327 def ignore_diskcheck_match(node, predicate, errorfmt):
330 def do_diskcheck_rcs(node, name):
332 rcs_dir = node.rcs_dir
333 except AttributeError:
334 rcs_dir = node.rcs_dir = node.Dir('RCS')
335 return rcs_dir.entry_exists_on_disk(name+',v')
337 def ignore_diskcheck_rcs(node, name):
340 def do_diskcheck_sccs(node, name):
342 sccs_dir = node.sccs_dir
343 except AttributeError:
344 sccs_dir = node.sccs_dir = node.Dir('SCCS')
345 return sccs_dir.entry_exists_on_disk('s.'+name)
347 def ignore_diskcheck_sccs(node, name):
350 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
351 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
352 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
360 def set_diskcheck(list):
361 for dc in diskcheckers:
364 def diskcheck_types():
365 return map(lambda dc: dc.type, diskcheckers)
369 class EntryProxy(SCons.Util.Proxy):
370 def __get_abspath(self):
372 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
373 entry.name + "_abspath")
375 def __get_filebase(self):
376 name = self.get().name
377 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
380 def __get_suffix(self):
381 name = self.get().name
382 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
385 def __get_file(self):
386 name = self.get().name
387 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
389 def __get_base_path(self):
390 """Return the file's directory and file name, with the
393 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
394 entry.name + "_base")
396 def __get_posix_path(self):
397 """Return the path with / as the path separator,
398 regardless of platform."""
403 r = string.replace(entry.get_path(), os.sep, '/')
404 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
406 def __get_win32_path(self):
407 """Return the path with \ as the path separator,
408 regardless of platform."""
413 r = string.replace(entry.get_path(), os.sep, '\\')
414 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_win32")
416 def __get_srcnode(self):
417 return EntryProxy(self.get().srcnode())
419 def __get_srcdir(self):
420 """Returns the directory containing the source node linked to this
421 node via BuildDir(), or the directory of this node if not linked."""
422 return EntryProxy(self.get().srcnode().dir)
424 def __get_rsrcnode(self):
425 return EntryProxy(self.get().srcnode().rfile())
427 def __get_rsrcdir(self):
428 """Returns the directory containing the source node linked to this
429 node via BuildDir(), or the directory of this node if not linked."""
430 return EntryProxy(self.get().srcnode().rfile().dir)
433 return EntryProxy(self.get().dir)
435 dictSpecialAttrs = { "base" : __get_base_path,
436 "posix" : __get_posix_path,
437 "win32" : __get_win32_path,
438 "srcpath" : __get_srcnode,
439 "srcdir" : __get_srcdir,
441 "abspath" : __get_abspath,
442 "filebase" : __get_filebase,
443 "suffix" : __get_suffix,
445 "rsrcpath" : __get_rsrcnode,
446 "rsrcdir" : __get_rsrcdir,
449 def __getattr__(self, name):
450 # This is how we implement the "special" attributes
451 # such as base, posix, srcdir, etc.
453 return self.dictSpecialAttrs[name](self)
456 attr = SCons.Util.Proxy.__getattr__(self, name)
457 except AttributeError:
459 classname = string.split(str(entry.__class__), '.')[-1]
460 if classname[-2:] == "'>":
461 # new-style classes report their name as:
462 # "<class 'something'>"
463 # instead of the classic classes:
465 classname = classname[:-2]
466 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
469 class Base(SCons.Node.Node):
470 """A generic class for file system entries. This class is for
471 when we don't know yet whether the entry being looked up is a file
472 or a directory. Instances of this class can morph into either
473 Dir or File objects by a later, more precise lookup.
475 Note: this class does not define __cmp__ and __hash__ for
476 efficiency reasons. SCons does a lot of comparing of
477 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
478 as fast as possible, which means we want to use Python's built-in
479 object identity comparisons.
482 def __init__(self, name, directory, fs):
483 """Initialize a generic Node.FS.Base object.
485 Call the superclass initialization, take care of setting up
486 our relative and absolute paths, identify our parent
487 directory, and indicate that this node should use
489 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
490 SCons.Node.Node.__init__(self)
495 assert directory, "A directory must be provided"
497 self.abspath = directory.entry_abspath(name)
498 if directory.path == '.':
501 self.path = directory.entry_path(name)
502 if directory.tpath == '.':
505 self.tpath = directory.entry_tpath(name)
506 self.path_elements = directory.path_elements + [self]
509 self.cwd = None # will hold the SConscript directory for target nodes
510 self.duplicate = directory.duplicate
513 """Completely clear a Node.FS.Base object of all its cached
514 state (so that it can be re-evaluated by interfaces that do
515 continuous integration builds).
518 SCons.Node.Node.clear(self)
523 def get_suffix(self):
525 return SCons.Util.splitext(self.name)[1]
531 """A Node.FS.Base object's string representation is its path
535 return self._save_str()
536 return self._get_str()
540 return self._get_str()
543 if self.duplicate or self.is_derived():
544 return self.get_path()
545 return self.srcnode().get_path()
551 try: return self.fs.stat(self.abspath)
552 except os.error: return None
556 return not self.stat() is None
560 return self.rfile().exists()
564 if st: return st[stat.ST_MTIME]
569 if st: return st[stat.ST_SIZE]
574 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
578 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
580 if hasattr(os, 'symlink'):
582 try: st = self.fs.lstat(self.abspath)
583 except os.error: return 0
584 return stat.S_ISLNK(st[stat.ST_MODE])
587 return 0 # no symlinks
589 def is_under(self, dir):
593 return self.dir.is_under(dir)
599 """If this node is in a build path, return the node
600 corresponding to its source file. Otherwise, return
607 srcnode = self.fs.Entry(name, dir.srcdir,
608 klass=self.__class__)
610 name = dir.name + os.sep + name
614 def get_path(self, dir=None):
615 """Return path relative to the current working directory of the
616 Node.FS.Base object that owns us."""
618 dir = self.fs.getcwd()
621 path_elems = self.path_elements
622 try: i = path_elems.index(dir)
623 except ValueError: pass
624 else: path_elems = path_elems[i+1:]
625 path_elems = map(lambda n: n.name, path_elems)
626 return string.join(path_elems, os.sep)
628 def set_src_builder(self, builder):
629 """Set the source code builder for this node."""
630 self.sbuilder = builder
631 if not self.has_builder():
632 self.builder_set(builder)
634 def src_builder(self):
635 """Fetch the source code builder for this node.
637 If there isn't one, we cache the source code builder specified
638 for the directory (which in turn will cache the value from its
639 parent directory, and so on up to the file system root).
643 except AttributeError:
644 scb = self.dir.src_builder()
648 def get_abspath(self):
649 """Get the absolute path of the file."""
652 def for_signature(self):
653 # Return just our name. Even an absolute path would not work,
654 # because that can change thanks to symlinks or remapped network
658 def get_subst_proxy(self):
661 except AttributeError:
662 ret = EntryProxy(self)
667 """This is the class for generic Node.FS entries--that is, things
668 that could be a File or a Dir, but we're just not sure yet.
669 Consequently, the methods in this class really exist just to
670 transform their associated object into the right class when the
671 time comes, and then call the same-named method in the transformed
674 def diskcheck_match(self):
677 def disambiguate(self):
682 self.__class__ = File
688 """We're a generic Entry, but the caller is actually looking for
689 a File at this point, so morph into one."""
690 self.__class__ = File
693 return File.rfile(self)
695 def get_found_includes(self, env, scanner, path):
696 """If we're looking for included files, it's because this Entry
697 is really supposed to be a File itself."""
698 return self.disambiguate().get_found_includes(env, scanner, path)
700 def scanner_key(self):
701 return self.get_suffix()
703 def get_contents(self):
704 """Fetch the contents of the entry.
706 Since this should return the real contents from the file
707 system, we check to see into what sort of subclass we should
710 self.__class__ = File
712 return self.get_contents()
716 return self.get_contents()
718 return '' # avoid errors for dangling symlinks
721 def rel_path(self, other):
722 return self.disambiguate().rel_path(other)
725 """Return if the Entry exists. Check the file system to see
726 what we should turn into first. Assume a file if there's no
728 return self.disambiguate().exists()
731 """Return if the Entry is missing. Check the file system to
732 see what we should turn into first. Assume a file if there's
734 return self.disambiguate().missing()
737 """Return the entry's content signature. Check the file system
738 to see what we should turn into first. Assume a file if there's
740 return self.disambiguate().get_csig()
742 def calc_signature(self, calc=None):
743 """Return the Entry's calculated signature. Check the file
744 system to see what we should turn into first. Assume a file if
745 there's no directory."""
746 return self.disambiguate().calc_signature(calc)
748 def must_be_a_Dir(self):
749 """Called to make sure a Node is a Dir. Since we're an
750 Entry, we can morph into one."""
755 # This is for later so we can differentiate between Entry the class and Entry
756 # the method of the FS class.
762 if SCons.Memoize.use_memoizer:
763 __metaclass__ = SCons.Memoize.Memoized_Metaclass
765 # This class implements an abstraction layer for operations involving
766 # a local file system. Essentially, this wraps any function in
767 # the os, os.path or shutil modules that we use to actually go do
768 # anything with or to the local file system.
770 # Note that there's a very good chance we'll refactor this part of
771 # the architecture in some way as we really implement the interface(s)
772 # for remote file system Nodes. For example, the right architecture
773 # might be to have this be a subclass instead of a base class.
774 # Nevertheless, we're using this as a first step in that direction.
776 # We're not using chdir() yet because the calling subclass method
777 # needs to use os.chdir() directly to avoid recursion. Will we
778 # really need this one?
779 #def chdir(self, path):
780 # return os.chdir(path)
781 def chmod(self, path, mode):
782 return os.chmod(path, mode)
783 def copy2(self, src, dst):
784 return shutil.copy2(src, dst)
785 def exists(self, path):
786 return os.path.exists(path)
787 def getmtime(self, path):
788 return os.path.getmtime(path)
789 def getsize(self, path):
790 return os.path.getsize(path)
791 def isdir(self, path):
792 return os.path.isdir(path)
793 def isfile(self, path):
794 return os.path.isfile(path)
795 def link(self, src, dst):
796 return os.link(src, dst)
797 def lstat(self, path):
798 return os.lstat(path)
799 def listdir(self, path):
800 return os.listdir(path)
801 def makedirs(self, path):
802 return os.makedirs(path)
803 def mkdir(self, path):
804 return os.mkdir(path)
805 def rename(self, old, new):
806 return os.rename(old, new)
807 def stat(self, path):
809 def symlink(self, src, dst):
810 return os.symlink(src, dst)
811 def open(self, path):
813 def unlink(self, path):
814 return os.unlink(path)
816 if hasattr(os, 'symlink'):
817 def islink(self, path):
818 return os.path.islink(path)
820 def islink(self, path):
821 return 0 # no symlinks
823 if SCons.Memoize.use_old_memoization():
825 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
826 def __init__(self, *args, **kw):
827 apply(_FSBase.__init__, (self,)+args, kw)
828 SCons.Memoize.Memoizer.__init__(self)
832 # # Skeleton for the obvious methods we might need from the
833 # # abstraction layer for a remote filesystem.
834 # def upload(self, local_src, remote_dst):
836 # def download(self, remote_src, local_dst):
842 def __init__(self, path = None):
843 """Initialize the Node.FS subsystem.
845 The supplied path is the top of the source tree, where we
846 expect to find the top-level build file. If no path is
847 supplied, the current directory is the default.
849 The path argument must be a valid absolute path.
851 if __debug__: logInstanceCreation(self, 'Node.FS')
854 self.SConstruct_dir = None
855 self.CachePath = None
856 self.cache_force = None
857 self.cache_show = None
858 self.max_drift = default_max_drift
862 self.pathTop = os.getcwd()
865 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
867 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
872 def clear_cache(self):
876 def set_SConstruct_dir(self, dir):
877 self.SConstruct_dir = dir
879 def get_max_drift(self):
880 return self.max_drift
882 def set_max_drift(self, max_drift):
883 self.max_drift = max_drift
888 def __checkClass(self, node, klass):
889 if isinstance(node, klass) or klass == Entry:
891 if node.__class__ == Entry:
892 node.__class__ = klass
895 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
896 (node.__class__.__name__, node.path, klass.__name__)
898 def _doLookup(self, fsclass, name, directory = None, create = 1):
899 """This method differs from the File and Dir factory methods in
900 one important way: the meaning of the directory parameter.
901 In this method, if directory is None or not supplied, the supplied
902 name is expected to be an absolute path. If you try to look up a
903 relative path with directory=None, then an AssertionError will be
908 # This is a stupid hack to compensate for the fact that
909 # the POSIX and Win32 versions of os.path.normpath() behave
910 # differently in older versions of Python. In particular,
912 # os.path.normpath('./') == '.'
914 # os.path.normpath('./') == ''
915 # os.path.normpath('.\\') == ''
917 # This is a definite bug in the Python library, but we have
920 path_orig = string.split(name, os.sep)
921 path_norm = string.split(_my_normcase(name), os.sep)
923 first_orig = path_orig.pop(0) # strip first element
924 first_norm = path_norm.pop(0) # strip first element
926 drive, path_first = os.path.splitdrive(first_orig)
928 path_orig = [ path_first, ] + path_orig
929 path_norm = [ _my_normcase(path_first), ] + path_norm
932 drive = _my_normcase(drive)
934 directory = self.Root[drive]
937 raise SCons.Errors.UserError
938 directory = RootDir(drive, self)
939 self.Root[drive] = directory
941 self.Root[self.defaultDrive] = directory
942 elif drive == self.defaultDrive:
943 self.Root[''] = directory
948 last_orig = path_orig.pop() # strip last element
949 last_norm = path_norm.pop() # strip last element
951 # Lookup the directory
952 for orig, norm in map(None, path_orig, path_norm):
954 entries = directory.entries
955 except AttributeError:
956 # We tried to look up the entry in either an Entry or
957 # a File. Give whatever it is a chance to do what's
958 # appropriate: morph into a Dir or raise an exception.
959 directory.must_be_a_Dir()
960 entries = directory.entries
962 directory = entries[norm]
965 raise SCons.Errors.UserError
967 d = Dir(orig, directory, self)
969 # Check the file system (or not, as configured) to make
970 # sure there isn't already a file there.
973 directory.entries[norm] = d
974 directory.add_wkid(d)
977 directory.must_be_a_Dir()
980 e = directory.entries[last_norm]
983 raise SCons.Errors.UserError
985 result = fsclass(last_orig, directory, self)
987 # Check the file system (or not, as configured) to make
988 # sure there isn't already a directory at the path on
989 # disk where we just created a File node, and vice versa.
990 result.diskcheck_match()
992 directory.entries[last_norm] = result
993 directory.add_wkid(result)
995 result = self.__checkClass(e, fsclass)
998 def _transformPath(self, name, directory):
999 """Take care of setting up the correct top-level directory,
1000 usually in preparation for a call to doLookup().
1002 If the path name is prepended with a '#', then it is unconditionally
1003 interpreted as relative to the top-level directory of this FS.
1005 If directory is None, and name is a relative path,
1006 then the same applies.
1008 if not SCons.Util.is_String(name):
1009 # This handles cases where the object is a Proxy wrapping
1010 # a Node.FS.File object (e.g.). It would be good to handle
1011 # this more directly some day by having the callers of this
1012 # function recognize that a Proxy can be treated like the
1013 # underlying object (that is, get rid of the isinstance()
1014 # calls that explicitly look for a Node.FS.Base object).
1016 if name and name[0] == '#':
1017 directory = self.Top
1019 if name and (name[0] == os.sep or name[0] == '/'):
1020 # Correct such that '#/foo' is equivalent
1023 name = os.path.join('.', os.path.normpath(name))
1025 directory = self._cwd
1026 return (os.path.normpath(name), directory)
1028 def chdir(self, dir, change_os_dir=0):
1029 """Change the current working directory for lookups.
1030 If change_os_dir is true, we will also change the "real" cwd
1038 os.chdir(dir.abspath)
1043 def Entry(self, name, directory = None, create = 1, klass=None):
1044 """Lookup or create a generic Entry node with the specified name.
1045 If the name is a relative path (begins with ./, ../, or a file
1046 name), then it is looked up relative to the supplied directory
1047 node, or to the top level directory of the FS (supplied at
1048 construction time) if no directory is supplied.
1054 if isinstance(name, Base):
1055 return self.__checkClass(name, klass)
1057 if directory and not isinstance(directory, Dir):
1058 directory = self.Dir(directory)
1059 name, directory = self._transformPath(name, directory)
1060 return self._doLookup(klass, name, directory, create)
1062 def File(self, name, directory = None, create = 1):
1063 """Lookup or create a File node with the specified name. If
1064 the name is a relative path (begins with ./, ../, or a file name),
1065 then it is looked up relative to the supplied directory node,
1066 or to the top level directory of the FS (supplied at construction
1067 time) if no directory is supplied.
1069 This method will raise TypeError if a directory is found at the
1073 return self.Entry(name, directory, create, File)
1075 def Dir(self, name, directory = None, create = 1):
1076 """Lookup or create a Dir 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 normal file is found at the
1086 return self.Entry(name, directory, create, Dir)
1088 def BuildDir(self, build_dir, src_dir, duplicate=1):
1089 """Link the supplied build directory to the source directory
1090 for purposes of building files."""
1092 if not isinstance(src_dir, SCons.Node.Node):
1093 src_dir = self.Dir(src_dir)
1094 if not isinstance(build_dir, SCons.Node.Node):
1095 build_dir = self.Dir(build_dir)
1096 if src_dir.is_under(build_dir):
1097 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1098 if build_dir.srcdir:
1099 if build_dir.srcdir == src_dir:
1100 return # We already did this.
1101 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1102 build_dir.link(src_dir, duplicate)
1104 def Repository(self, *dirs):
1105 """Specify Repository directories to search."""
1107 if not isinstance(d, SCons.Node.Node):
1109 self.Top.addRepository(d)
1111 def Rfindalldirs(self, pathlist, cwd):
1113 if SCons.Util.is_String(pathlist):
1114 pathlist = string.split(pathlist, os.pathsep)
1115 if not SCons.Util.is_List(pathlist):
1116 pathlist = [pathlist]
1118 for path in filter(None, pathlist):
1119 if isinstance(path, SCons.Node.Node):
1122 path, dir = self._transformPath(path, cwd)
1124 result.extend(dir.get_all_rdirs())
1127 def CacheDir(self, path):
1128 self.CachePath = path
1130 def build_dir_target_climb(self, orig, dir, tail):
1131 """Create targets in corresponding build directories
1133 Climb the directory tree, and look up path names
1134 relative to any linked build directories we find.
1139 fmt = "building associated BuildDir targets: %s"
1142 for bd in dir.build_dirs:
1143 if start_dir.is_under(bd):
1144 # If already in the build-dir location, don't reflect
1145 return [orig], fmt % str(orig)
1146 p = apply(os.path.join, [bd.path] + tail)
1147 targets.append(self.Entry(p))
1148 tail = [dir.name] + tail
1151 message = fmt % string.join(map(str, targets))
1152 return targets, message
1155 """A class for directories in a file system.
1158 def __init__(self, name, directory, fs):
1159 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1160 Base.__init__(self, name, directory, fs)
1164 """Turn a file system Node (either a freshly initialized directory
1165 object or a separate Entry object) into a proper directory object.
1167 Set up this directory's entries and hook it into the file
1168 system tree. Specify that directories (this Node) don't use
1169 signatures for calculating whether they're current.
1172 self.repositories = []
1176 self.entries['.'] = self
1177 self.entries['..'] = self.dir
1180 self._sconsign = None
1181 self.build_dirs = []
1183 # Don't just reset the executor, replace its action list,
1184 # because it might have some pre-or post-actions that need to
1186 self.builder = get_MkdirBuilder()
1187 self.get_executor().set_action_list(self.builder.action)
1189 def diskcheck_match(self):
1190 diskcheck_match(self, self.fs.isfile,
1191 "File %s found where directory expected.")
1193 def disambiguate(self):
1196 def __clearRepositoryCache(self, duplicate=None):
1197 """Called when we change the repository(ies) for a directory.
1198 This clears any cached information that is invalidated by changing
1201 for node in self.entries.values():
1202 if node != self.dir:
1203 if node != self and isinstance(node, Dir):
1204 node.__clearRepositoryCache(duplicate)
1209 except AttributeError:
1211 if duplicate != None:
1212 node.duplicate=duplicate
1214 def __resetDuplicate(self, node):
1216 node.duplicate = node.get_dir().duplicate
1218 def Entry(self, name):
1219 """Create an entry node named 'name' relative to this directory."""
1220 return self.fs.Entry(name, self)
1222 def Dir(self, name):
1223 """Create a directory node named 'name' relative to this directory."""
1224 return self.fs.Dir(name, self)
1226 def File(self, name):
1227 """Create a file node named 'name' relative to this directory."""
1228 return self.fs.File(name, self)
1230 def link(self, srcdir, duplicate):
1231 """Set this directory as the build directory for the
1232 supplied source directory."""
1233 self.srcdir = srcdir
1234 self.duplicate = duplicate
1235 self.__clearRepositoryCache(duplicate)
1236 srcdir.build_dirs.append(self)
1238 def getRepositories(self):
1239 """Returns a list of repositories for this directory.
1241 if self.srcdir and not self.duplicate:
1242 return self.srcdir.get_all_rdirs() + self.repositories
1243 return self.repositories
1245 def get_all_rdirs(self):
1251 for rep in dir.getRepositories():
1252 result.append(rep.Dir(fname))
1253 fname = dir.name + os.sep + fname
1257 def addRepository(self, dir):
1258 if dir != self and not dir in self.repositories:
1259 self.repositories.append(dir)
1261 self.__clearRepositoryCache()
1264 return self.entries['..']
1266 def rel_path(self, other):
1267 """Return a path to "other" relative to this directory.
1269 if isinstance(other, Dir):
1275 except AttributeError:
1278 return name and name[0] or '.'
1280 for x, y in map(None, self.path_elements, other.path_elements):
1284 path_elems = ['..']*(len(self.path_elements)-i) \
1285 + map(lambda n: n.name, other.path_elements[i:]) \
1288 return string.join(path_elems, os.sep)
1291 if not self.implicit is None:
1294 self.implicit_dict = {}
1295 self._children_reset()
1297 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1298 deps = filter(dont_scan, self.entries.keys())
1299 # keys() is going to give back the entries in an internal,
1300 # unsorted order. Sort 'em so the order is deterministic.
1302 entries = map(lambda n, e=self.entries: e[n], deps)
1304 self._add_child(self.implicit, self.implicit_dict, entries)
1306 def get_found_includes(self, env, scanner, path):
1307 """Return the included implicit dependencies in this file.
1308 Cache results so we only scan the file once per path
1309 regardless of how many times this information is requested.
1313 # Clear cached info for this Node. If we already visited this
1314 # directory on our walk down the tree (because we didn't know at
1315 # that point it was being used as the source for another Node)
1316 # then we may have calculated build signature before realizing
1317 # we had to scan the disk. Now that we have to, though, we need
1318 # to invalidate the old calculated signature so that any node
1319 # dependent on our directory structure gets one that includes
1320 # info about everything on disk.
1322 return scanner(self, env, path)
1324 def build(self, **kw):
1325 """A null "builder" for directories."""
1327 if not self.builder is MkdirBuilder:
1328 apply(SCons.Node.Node.build, [self,], kw)
1331 """Create this directory, silently and without worrying about
1332 whether the builder is the default or not."""
1338 listDirs.append(parent)
1341 raise SCons.Errors.StopError, parent.path
1344 for dirnode in listDirs:
1346 # Don't call dirnode.build(), call the base Node method
1347 # directly because we definitely *must* create this
1348 # directory. The dirnode.build() method will suppress
1349 # the build if it's the default builder.
1350 SCons.Node.Node.build(dirnode)
1351 dirnode.get_executor().nullify()
1352 # The build() action may or may not have actually
1353 # created the directory, depending on whether the -n
1354 # option was used or not. Delete the _exists and
1355 # _rexists attributes so they can be reevaluated.
1360 def multiple_side_effect_has_builder(self):
1362 return not self.builder is MkdirBuilder and self.has_builder()
1364 def alter_targets(self):
1365 """Return any corresponding targets in a build directory.
1367 return self.fs.build_dir_target_climb(self, self, [])
1369 def scanner_key(self):
1370 """A directory does not get scanned."""
1373 def get_contents(self):
1374 """Return aggregate contents of all our children."""
1375 contents = map(lambda n: n.get_contents(), self.children())
1376 return string.join(contents, '')
1381 def do_duplicate(self, src):
1384 def current(self, calc=None):
1385 """If all of our children were up-to-date, then this
1386 directory was up-to-date, too."""
1387 if not self.builder is MkdirBuilder and not self.exists():
1390 for kid in self.children():
1392 if s and (not state or s > state):
1395 if state == 0 or state == SCons.Node.up_to_date:
1402 if not self.exists():
1403 norm_name = _my_normcase(self.name)
1404 for dir in self.dir.get_all_rdirs():
1405 try: node = dir.entries[norm_name]
1406 except KeyError: node = dir.dir_on_disk(self.name)
1407 if node and node.exists() and \
1408 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1413 """Return the .sconsign file info for this directory,
1414 creating it first if necessary."""
1415 if not self._sconsign:
1416 import SCons.SConsign
1417 self._sconsign = SCons.SConsign.ForDirectory(self)
1418 return self._sconsign
1421 """Dir has a special need for srcnode()...if we
1422 have a srcdir attribute set, then that *is* our srcnode."""
1425 return Base.srcnode(self)
1427 def get_timestamp(self):
1428 """Return the latest timestamp from among our children"""
1430 for kid in self.children():
1431 if kid.get_timestamp() > stamp:
1432 stamp = kid.get_timestamp()
1435 def entry_abspath(self, name):
1436 return self.abspath + os.sep + name
1438 def entry_path(self, name):
1439 return self.path + os.sep + name
1441 def entry_tpath(self, name):
1442 return self.tpath + os.sep + name
1444 def must_be_a_Dir(self):
1445 """Called to make sure a Node is a Dir. Since we're already
1446 one, this is a no-op for us."""
1449 def entry_exists_on_disk(self, name):
1452 d = self.on_disk_entries
1453 except AttributeError:
1456 entries = os.listdir(self.abspath)
1460 for entry in map(_my_normcase, entries):
1462 self.on_disk_entries = d
1463 return d.has_key(_my_normcase(name))
1465 def srcdir_list(self):
1473 d = dir.srcdir.Dir(dirname)
1475 # Shouldn't source from something in the build path:
1476 # build_dir is probably under src_dir, in which case
1477 # we are reflecting.
1480 dirname = dir.name + os.sep + dirname
1485 def srcdir_duplicate(self, name):
1486 for dir in self.srcdir_list():
1487 if dir.entry_exists_on_disk(name):
1488 srcnode = dir.File(name)
1490 node = self.File(name)
1491 node.do_duplicate(srcnode)
1497 def srcdir_find_file(self, filename):
1500 if (isinstance(node, File) or isinstance(node, Entry)) and \
1501 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1505 norm_name = _my_normcase(filename)
1507 for rdir in self.get_all_rdirs():
1508 try: node = rdir.entries[norm_name]
1509 except KeyError: node = rdir.file_on_disk(filename)
1510 else: node = func(node)
1514 for srcdir in self.srcdir_list():
1515 for rdir in srcdir.get_all_rdirs():
1516 try: node = rdir.entries[norm_name]
1517 except KeyError: node = rdir.file_on_disk(filename)
1518 else: node = func(node)
1520 return File(filename, self, self.fs), srcdir
1524 def dir_on_disk(self, name):
1525 if self.entry_exists_on_disk(name):
1526 try: return self.Dir(name)
1527 except TypeError: pass
1530 def file_on_disk(self, name):
1531 if self.entry_exists_on_disk(name) or \
1532 diskcheck_rcs(self, name) or \
1533 diskcheck_sccs(self, name):
1534 try: return self.File(name)
1535 except TypeError: pass
1536 return self.srcdir_duplicate(name)
1539 """A class for the root directory of a file system.
1541 This is the same as a Dir class, except that the path separator
1542 ('/' or '\\') is actually part of the name, so we don't need to
1543 add a separator when creating the path names of entries within
1546 def __init__(self, name, fs):
1547 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1548 # We're going to be our own parent directory (".." entry and .dir
1549 # attribute) so we have to set up some values so Base.__init__()
1550 # won't gag won't it calls some of our methods.
1554 self.path_elements = []
1556 Base.__init__(self, name, self, fs)
1558 # Now set our paths to what we really want them to be: the
1559 # initial drive letter (the name) plus the directory separator.
1560 self.abspath = name + os.sep
1561 self.path = name + os.sep
1562 self.tpath = name + os.sep
1568 def entry_abspath(self, name):
1569 return self.abspath + name
1571 def entry_path(self, name):
1572 return self.path + name
1574 def entry_tpath(self, name):
1575 return self.tpath + name
1577 def is_under(self, dir):
1589 def src_builder(self):
1592 class NodeInfo(SCons.Node.NodeInfo):
1593 def __cmp__(self, other):
1594 try: return cmp(self.bsig, other.bsig)
1595 except AttributeError: return 1
1596 def update(self, node):
1597 self.timestamp = node.get_timestamp()
1598 self.size = node.getsize()
1600 class BuildInfo(SCons.Node.BuildInfo):
1601 def __init__(self, node):
1602 SCons.Node.BuildInfo.__init__(self, node)
1604 def convert_to_sconsign(self):
1605 """Convert this BuildInfo object for writing to a .sconsign file
1607 We hung onto the node that we refer to so that we can translate
1608 the lists of bsources, bdepends and bimplicit Nodes into strings
1609 relative to the node, but we don't want to write out that Node
1610 itself to the .sconsign file, so we delete the attribute in
1613 rel_path = self.node.rel_path
1614 delattr(self, 'node')
1615 for attr in ['bsources', 'bdepends', 'bimplicit']:
1617 val = getattr(self, attr)
1618 except AttributeError:
1621 setattr(self, attr, map(rel_path, val))
1622 def convert_from_sconsign(self, dir, name):
1623 """Convert a newly-read BuildInfo object for in-SCons use
1625 An on-disk BuildInfo comes without a reference to the node
1626 for which it's intended, so we have to convert the arguments
1627 and add back a self.node attribute. The bsources, bdepends and
1628 bimplicit lists all come from disk as paths relative to that node,
1629 so convert them to actual Nodes for use by the rest of SCons.
1631 self.node = dir.Entry(name)
1632 Entry_func = self.node.dir.Entry
1633 for attr in ['bsources', 'bdepends', 'bimplicit']:
1635 val = getattr(self, attr)
1636 except AttributeError:
1639 setattr(self, attr, map(Entry_func, val))
1641 result = [ self.ninfo.format() ]
1642 bkids = self.bsources + self.bdepends + self.bimplicit
1643 bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
1644 for i in xrange(len(bkids)):
1645 result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
1646 return string.join(result, '\n')
1650 """A class for files in a file system.
1652 def diskcheck_match(self):
1653 diskcheck_match(self, self.fs.isdir,
1654 "Directory %s found where file expected.")
1656 def __init__(self, name, directory, fs):
1657 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1658 Base.__init__(self, name, directory, fs)
1661 def Entry(self, name):
1662 """Create an entry node named 'name' relative to
1663 the SConscript directory of this file."""
1664 return self.fs.Entry(name, self.cwd)
1666 def Dir(self, name):
1667 """Create a directory node named 'name' relative to
1668 the SConscript directory of this file."""
1669 return self.fs.Dir(name, self.cwd)
1671 def Dirs(self, pathlist):
1672 """Create a list of directories relative to the SConscript
1673 directory of this file."""
1674 return map(lambda p, s=self: s.Dir(p), pathlist)
1676 def File(self, name):
1677 """Create a file node named 'name' relative to
1678 the SConscript directory of this file."""
1679 return self.fs.File(name, self.cwd)
1681 def RDirs(self, pathlist):
1682 """Search for a list of directories in the Repository list."""
1683 return self.fs.Rfindalldirs(pathlist, self.cwd)
1685 #def generate_build_dict(self):
1686 # """Return an appropriate dictionary of values for building
1688 # return {'Dir' : self.Dir,
1689 # 'File' : self.File,
1690 # 'RDirs' : self.RDirs}
1693 """Turn a file system node into a File object. __cache_reset__"""
1694 self.scanner_paths = {}
1695 if not hasattr(self, '_local'):
1698 def disambiguate(self):
1701 def scanner_key(self):
1702 return self.get_suffix()
1704 def get_contents(self):
1705 if not self.rexists():
1707 return open(self.rfile().abspath, "rb").read()
1709 def get_timestamp(self):
1711 return self.rfile().getmtime()
1715 def store_info(self, obj):
1716 # Merge our build information into the already-stored entry.
1717 # This accomodates "chained builds" where a file that's a target
1718 # in one build (SConstruct file) is a source in a different build.
1719 # See test/chained-build.py for the use case.
1720 entry = self.get_stored_info()
1722 self.dir.sconsign().set_entry(self.name, entry)
1724 def get_stored_info(self):
1727 stored = self.dir.sconsign().get_entry(self.name)
1728 except (KeyError, OSError):
1729 return self.new_binfo()
1731 if not hasattr(stored, 'ninfo'):
1732 # Transition: The .sconsign file entry has no NodeInfo
1733 # object, which means it's a slightly older BuildInfo.
1734 # Copy over the relevant attributes.
1735 ninfo = stored.ninfo = self.new_ninfo()
1736 for attr in ninfo.__dict__.keys():
1738 setattr(ninfo, attr, getattr(stored, attr))
1739 except AttributeError:
1743 def get_stored_implicit(self):
1744 binfo = self.get_stored_info()
1745 try: return binfo.bimplicit
1746 except AttributeError: return None
1748 def rel_path(self, other):
1749 return self.dir.rel_path(other)
1751 def get_found_includes(self, env, scanner, path):
1752 """Return the included implicit dependencies in this file.
1753 Cache results so we only scan the file once per path
1754 regardless of how many times this information is requested.
1758 return scanner(self, env, path)
1760 def _createDir(self):
1761 # ensure that the directories for this node are
1765 def retrieve_from_cache(self):
1766 """Try to retrieve the node's content from a cache
1768 This method is called from multiple threads in a parallel build,
1769 so only do thread safe stuff here. Do thread unsafe stuff in
1772 Note that there's a special trick here with the execute flag
1773 (one that's not normally done for other actions). Basically
1774 if the user requested a noexec (-n) build, then
1775 SCons.Action.execute_actions is set to 0 and when any action
1776 is called, it does its showing but then just returns zero
1777 instead of actually calling the action execution operation.
1778 The problem for caching is that if the file does NOT exist in
1779 cache then the CacheRetrieveString won't return anything to
1780 show for the task, but the Action.__call__ won't call
1781 CacheRetrieveFunc; instead it just returns zero, which makes
1782 the code below think that the file *was* successfully
1783 retrieved from the cache, therefore it doesn't do any
1784 subsequent building. However, the CacheRetrieveString didn't
1785 print anything because it didn't actually exist in the cache,
1786 and no more build actions will be performed, so the user just
1787 sees nothing. The fix is to tell Action.__call__ to always
1788 execute the CacheRetrieveFunc and then have the latter
1789 explicitly check SCons.Action.execute_actions itself.
1791 Returns true iff the node was successfully retrieved.
1793 b = self.is_derived()
1794 if not b and not self.has_src_builder():
1796 if b and self.fs.CachePath:
1797 if self.fs.cache_show:
1798 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1799 self.build(presub=0, execute=0)
1801 elif CacheRetrieve(self, [], None, execute=1) == 0:
1806 """Called just after this node is successfully built.
1808 # Push this file out to cache before the superclass Node.built()
1809 # method has a chance to clear the build signature, which it
1810 # will do if this file has a source scanner.
1811 if self.fs.CachePath and self.exists():
1812 CachePush(self, [], None)
1813 self.fs.clear_cache()
1814 SCons.Node.Node.built(self)
1817 if self.fs.CachePath and self.fs.cache_force and self.exists():
1818 CachePush(self, None, None)
1820 def has_src_builder(self):
1821 """Return whether this Node has a source builder or not.
1823 If this Node doesn't have an explicit source code builder, this
1824 is where we figure out, on the fly, if there's a transparent
1825 source code builder for it.
1827 Note that if we found a source builder, we also set the
1828 self.builder attribute, so that all of the methods that actually
1829 *build* this file don't have to do anything different.
1833 except AttributeError:
1837 scb = self.dir.src_builder()
1839 if diskcheck_sccs(self.dir, self.name):
1840 scb = get_DefaultSCCSBuilder()
1841 elif diskcheck_rcs(self.dir, self.name):
1842 scb = get_DefaultRCSBuilder()
1846 self.builder_set(scb)
1848 return not scb is None
1850 def alter_targets(self):
1851 """Return any corresponding targets in a build directory.
1853 if self.is_derived():
1855 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1857 def is_pseudo_derived(self):
1859 return self.has_src_builder()
1861 def _rmv_existing(self):
1863 Unlink(self, [], None)
1866 """Prepare for this file to be created."""
1867 SCons.Node.Node.prepare(self)
1869 if self.get_state() != SCons.Node.up_to_date:
1871 if self.is_derived() and not self.precious:
1872 self._rmv_existing()
1876 except SCons.Errors.StopError, drive:
1877 desc = "No drive `%s' for target `%s'." % (drive, self)
1878 raise SCons.Errors.StopError, desc
1881 """Remove this file."""
1882 if self.exists() or self.islink():
1883 self.fs.unlink(self.path)
1887 def do_duplicate(self, src):
1890 Unlink(self, None, None)
1891 except SCons.Errors.BuildError:
1894 Link(self, src, None)
1895 except SCons.Errors.BuildError, e:
1896 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1897 raise SCons.Errors.StopError, desc
1899 # The Link() action may or may not have actually
1900 # created the file, depending on whether the -n
1901 # option was used or not. Delete the _exists and
1902 # _rexists attributes so they can be reevaluated.
1907 # Duplicate from source path if we are set up to do this.
1908 if self.duplicate and not self.is_derived() and not self.linked:
1909 src = self.srcnode()
1911 return Base.exists(self)
1912 # At this point, src is meant to be copied in a build directory.
1914 if src.abspath != self.abspath:
1916 self.do_duplicate(src)
1917 # Can't return 1 here because the duplication might
1918 # not actually occur if the -n option is being used.
1920 # The source file does not exist. Make sure no old
1921 # copy remains in the build directory.
1922 if Base.exists(self) or self.islink():
1923 self.fs.unlink(self.path)
1924 # Return None explicitly because the Base.exists() call
1925 # above will have cached its value if the file existed.
1927 return Base.exists(self)
1930 # SIGNATURE SUBSYSTEM
1933 def new_binfo(self):
1934 return BuildInfo(self)
1936 def new_ninfo(self):
1941 def get_csig(self, calc=None):
1943 Generate a node's content signature, the digested signature
1947 cache - alternate node to use for the signature cache
1948 returns - the content signature
1951 return self.binfo.ninfo.csig
1952 except AttributeError:
1956 calc = self.calculator()
1958 max_drift = self.fs.max_drift
1959 mtime = self.get_timestamp()
1960 use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
1964 old = self.get_stored_info().ninfo
1966 if old.timestamp and old.csig and old.timestamp == mtime:
1968 except AttributeError:
1971 csig = calc.module.signature(self)
1973 binfo = self.get_binfo()
1979 self.store_info(binfo)
1987 def current(self, calc=None):
1988 self.binfo = self.gen_binfo(calc)
1992 if self.always_build:
1994 if not self.exists():
1995 # The file doesn't exist locally...
1998 # ...but there is one in a Repository...
1999 old = r.get_stored_info()
2000 new = self.get_binfo()
2002 # ...and it's even up-to-date...
2004 # ...and they'd like a local copy.
2005 LocalCopy(self, r, None)
2006 self.store_info(new)
2010 old = self.get_stored_info()
2011 new = self.get_binfo()
2016 if not self.exists():
2017 norm_name = _my_normcase(self.name)
2018 for dir in self.dir.get_all_rdirs():
2019 try: node = dir.entries[norm_name]
2020 except KeyError: node = dir.file_on_disk(self.name)
2021 if node and node.exists() and \
2022 (isinstance(node, File) or isinstance(node, Entry) \
2023 or not node.is_derived()):
2028 return str(self.rfile())
2030 def cachepath(self):
2031 if not self.fs.CachePath:
2033 ninfo = self.get_binfo().ninfo
2034 if not hasattr(ninfo, 'bsig'):
2035 raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
2036 elif ninfo.bsig is None:
2037 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
2038 # Add the path to the cache signature, because multiple
2039 # targets built by the same action will all have the same
2040 # build signature, and we have to differentiate them somehow.
2041 cache_sig = SCons.Sig.MD5.collect([ninfo.bsig, self.path])
2042 subdir = string.upper(cache_sig[0])
2043 dir = os.path.join(self.fs.CachePath, subdir)
2044 return dir, os.path.join(dir, cache_sig)
2046 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
2047 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
2049 def must_be_a_Dir(self):
2050 """Called to make sure a Node is a Dir. Since we're already a
2051 File, this is a TypeError..."""
2052 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
2056 def find_file(filename, paths, verbose=None):
2058 find_file(str, [Dir()]) -> [nodes]
2060 filename - a filename to find
2061 paths - a list of directory path *nodes* to search in. Can be
2062 represented as a list, a tuple, or a callable that is
2063 called with no arguments and returns the list or tuple.
2065 returns - the node created from the found file.
2067 Find a node corresponding to either a derived file or a file
2068 that exists already.
2070 Only the first file found is returned, and none is returned
2071 if no file is found.
2075 if not SCons.Util.is_String(verbose):
2076 verbose = "find_file"
2077 if not callable(verbose):
2078 verbose = ' %s: ' % verbose
2079 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2081 verbose = lambda x: x
2086 # Give Entries a chance to morph into Dirs.
2087 paths = map(lambda p: p.must_be_a_Dir(), paths)
2089 filedir, filename = os.path.split(filename)
2091 def filedir_lookup(p, fd=filedir):
2095 # We tried to look up a Dir, but it seems there's already
2096 # a File (or something else) there. No big.
2098 paths = filter(None, map(filedir_lookup, paths))
2101 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2102 node, d = dir.srcdir_find_file(filename)
2104 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2108 def find_files(filenames, paths):
2110 find_files([str], [Dir()]) -> [nodes]
2112 filenames - a list of filenames to find
2113 paths - a list of directory path *nodes* to search in
2115 returns - the nodes created from the found files.
2117 Finds nodes corresponding to either derived files or files
2120 Only the first file found is returned for each filename,
2121 and any files that aren't found are ignored.
2123 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
2124 return filter(None, nodes)