5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
8 This initializes a "default_fs" Node with an FS at the current directory
9 for its own purposes, and for use by scripts or modules looking for the
17 # Permission is hereby granted, free of charge, to any person obtaining
18 # a copy of this software and associated documentation files (the
19 # "Software"), to deal in the Software without restriction, including
20 # without limitation the rights to use, copy, modify, merge, publish,
21 # distribute, sublicense, and/or sell copies of the Software, and to
22 # permit persons to whom the Software is furnished to do so, subject to
23 # the following conditions:
25 # The above copyright notice and this permission notice shall be included
26 # in all copies or substantial portions of the Software.
28 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
29 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
30 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
49 from SCons.Debug import logInstanceCreation
57 # We stringify these file system Nodes a lot. Turning a file system Node
58 # into a string is non-trivial, because the final string representation
59 # can depend on a lot of factors: whether it's a derived target or not,
60 # whether it's linked to a repository or source directory, and whether
61 # there's duplication going on. The normal technique for optimizing
62 # calculations like this is to memoize (cache) the string value, so you
63 # only have to do the calculation once.
65 # A number of the above factors, however, can be set after we've already
66 # been asked to return a string for a Node, because a Repository() or
67 # BuildDir() call or the like may not occur until later in SConscript
68 # files. So this variable controls whether we bother trying to save
69 # string values for Nodes. The wrapper interface can set this whenever
70 # they're done mucking with Repository and BuildDir and the other stuff,
71 # to let this module know it can start returning saved string values
76 def save_strings(val):
81 # SCons.Action objects for interacting with the outside world.
83 # The Node.FS methods in this module should use these actions to
84 # create and/or remove files and directories; they should *not* use
85 # os.{link,symlink,unlink,mkdir}(), etc., directly.
87 # Using these SCons.Action objects ensures that descriptions of these
88 # external activities are properly displayed, that the displays are
89 # suppressed when the -s (silent) option is used, and (most importantly)
90 # the actions are disabled when the the -n option is used, in which case
91 # there should be *no* changes to the external file system(s)...
94 def _copy_func(src, dest):
95 shutil.copy2(src, dest)
97 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
99 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
100 'hard-copy', 'soft-copy', 'copy']
102 Link_Funcs = [] # contains the callables of the specified duplication style
104 def set_duplicate(duplicate):
105 # Fill in the Link_Funcs list according to the argument
106 # (discarding those not available on the platform).
108 # Set up the dictionary that maps the argument names to the
109 # underlying implementations. We do this inside this function,
110 # not in the top-level module code, so that we can remap os.link
111 # and os.symlink for testing purposes.
113 _hardlink_func = os.link
114 except AttributeError:
115 _hardlink_func = None
118 _softlink_func = os.symlink
119 except AttributeError:
120 _softlink_func = None
123 'hard' : _hardlink_func,
124 'soft' : _softlink_func,
128 if not duplicate in Valid_Duplicates:
129 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
130 "should be in Valid_Duplicates")
133 for func in string.split(duplicate,'-'):
135 Link_Funcs.append(link_dict[func])
137 def LinkFunc(target, source, env):
138 # Relative paths cause problems with symbolic links, so
139 # we use absolute paths, which may be a problem for people
140 # who want to move their soft-linked src-trees around. Those
141 # people should use the 'hard-copy' mode, softlinks cannot be
142 # used for that; at least I have no idea how ...
143 src = source[0].abspath
144 dest = target[0].abspath
145 dir, file = os.path.split(dest)
146 if dir and not os.path.isdir(dir):
149 # Set a default order of link functions.
150 set_duplicate('hard-soft-copy')
151 # Now link the files with the previously specified order.
152 for func in Link_Funcs:
157 if func == Link_Funcs[-1]:
158 # exception of the last link method (copy) are fatal
164 Link = SCons.Action.Action(LinkFunc, None)
165 def LocalString(target, source, env):
166 return 'Local copy of %s from %s' % (target[0], source[0])
168 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
170 def UnlinkFunc(target, source, env):
172 t.fs.unlink(t.abspath)
175 Unlink = SCons.Action.Action(UnlinkFunc, None)
177 def MkdirFunc(target, source, env):
180 if not t.fs.exists(p):
184 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
188 def get_MkdirBuilder():
190 if MkdirBuilder is None:
192 # "env" will get filled in by Executor.get_build_env()
193 # calling SCons.Defaults.DefaultEnvironment() when necessary.
194 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
198 name = "MkdirBuilder")
201 def CacheRetrieveFunc(target, source, env):
204 cachedir, cachefile = t.cachepath()
205 if fs.exists(cachefile):
206 if SCons.Action.execute_actions:
207 fs.copy2(cachefile, t.path)
208 st = fs.stat(cachefile)
209 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
213 def CacheRetrieveString(target, source, env):
215 cachedir, cachefile = t.cachepath()
216 if t.fs.exists(cachefile):
217 return "Retrieved `%s' from cache" % t.path
220 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
222 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
224 def CachePushFunc(target, source, env):
227 cachedir, cachefile = t.cachepath()
228 if fs.exists(cachefile):
229 # Don't bother copying it if it's already there.
232 if not fs.isdir(cachedir):
233 fs.makedirs(cachedir)
235 tempfile = cachefile+'.tmp'
237 fs.copy2(t.path, tempfile)
238 fs.rename(tempfile, cachefile)
240 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
242 # It's possible someone else tried writing the file at the same
243 # time we did. Print a warning but don't stop the build, since
244 # it doesn't affect the correctness of the build.
245 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
246 "Unable to copy %s to cache. Cache file is %s"
247 % (str(target), cachefile))
250 CachePush = SCons.Action.Action(CachePushFunc, None)
257 DefaultSCCSBuilder = None
258 DefaultRCSBuilder = None
260 def get_DefaultSCCSBuilder():
261 global DefaultSCCSBuilder
262 if DefaultSCCSBuilder is None:
264 # "env" will get filled in by Executor.get_build_env()
265 # calling SCons.Defaults.DefaultEnvironment() when necessary.
266 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
267 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
269 name = "DefaultSCCSBuilder")
270 return DefaultSCCSBuilder
272 def get_DefaultRCSBuilder():
273 global DefaultRCSBuilder
274 if DefaultRCSBuilder is None:
276 # "env" will get filled in by Executor.get_build_env()
277 # calling SCons.Defaults.DefaultEnvironment() when necessary.
278 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
279 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
281 name = "DefaultRCSBuilder")
282 return DefaultRCSBuilder
287 An instance of this class is used as the parent of the root of a
288 filesystem (POSIX) or drive (Win32). This isn't actually a node,
289 but it looks enough like one so that we don't have to have
290 special purpose code everywhere to deal with dir being None.
291 This class is an instance of the Null object pattern.
301 def is_under(self, dir):
307 def getRepositories(self):
313 def src_builder(self):
316 def entry_abspath(self, name):
319 def entry_path(self, name):
322 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
323 _is_cygwin = sys.platform == "cygwin"
324 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
329 return string.upper(x)
331 class EntryProxy(SCons.Util.Proxy):
332 def __get_abspath(self):
334 return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
335 entry.name + "_abspath")
337 def __get_filebase(self):
338 name = self.get().name
339 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
342 def __get_suffix(self):
343 name = self.get().name
344 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
347 def __get_file(self):
348 name = self.get().name
349 return SCons.Util.SpecialAttrWrapper(name, name + "_file")
351 def __get_base_path(self):
352 """Return the file's directory and file name, with the
355 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
356 entry.name + "_base")
358 def __get_posix_path(self):
359 """Return the path with / as the path separator,
360 regardless of platform."""
365 r = string.replace(entry.get_path(), os.sep, '/')
366 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_posix")
368 def __get_win32_path(self):
369 """Return the path with \ as the path separator,
370 regardless of platform."""
375 r = string.replace(entry.get_path(), os.sep, '\\')
376 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_win32")
378 def __get_srcnode(self):
379 return EntryProxy(self.get().srcnode())
381 def __get_srcdir(self):
382 """Returns the directory containing the source node linked to this
383 node via BuildDir(), or the directory of this node if not linked."""
384 return EntryProxy(self.get().srcnode().dir)
386 def __get_rsrcnode(self):
387 return EntryProxy(self.get().srcnode().rfile())
389 def __get_rsrcdir(self):
390 """Returns the directory containing the source node linked to this
391 node via BuildDir(), or the directory of this node if not linked."""
392 return EntryProxy(self.get().srcnode().rfile().dir)
395 return EntryProxy(self.get().dir)
397 dictSpecialAttrs = { "base" : __get_base_path,
398 "posix" : __get_posix_path,
399 "win32" : __get_win32_path,
400 "srcpath" : __get_srcnode,
401 "srcdir" : __get_srcdir,
403 "abspath" : __get_abspath,
404 "filebase" : __get_filebase,
405 "suffix" : __get_suffix,
407 "rsrcpath" : __get_rsrcnode,
408 "rsrcdir" : __get_rsrcdir,
411 def __getattr__(self, name):
412 # This is how we implement the "special" attributes
413 # such as base, posix, srcdir, etc.
415 return self.dictSpecialAttrs[name](self)
418 attr = SCons.Util.Proxy.__getattr__(self, name)
419 except AttributeError:
421 classname = string.split(str(entry.__class__), '.')[-1]
422 if classname[-2:] == "'>":
423 # new-style classes report their name as:
424 # "<class 'something'>"
425 # instead of the classic classes:
427 classname = classname[:-2]
428 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
431 class Base(SCons.Node.Node):
432 """A generic class for file system entries. This class is for
433 when we don't know yet whether the entry being looked up is a file
434 or a directory. Instances of this class can morph into either
435 Dir or File objects by a later, more precise lookup.
437 Note: this class does not define __cmp__ and __hash__ for
438 efficiency reasons. SCons does a lot of comparing of
439 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
440 as fast as possible, which means we want to use Python's built-in
441 object identity comparisons.
444 def __init__(self, name, directory, fs):
445 """Initialize a generic Node.FS.Base object.
447 Call the superclass initialization, take care of setting up
448 our relative and absolute paths, identify our parent
449 directory, and indicate that this node should use
451 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
452 SCons.Node.Node.__init__(self)
457 assert directory, "A directory must be provided"
459 self.abspath = directory.entry_abspath(name)
460 if directory.path == '.':
463 self.path = directory.entry_path(name)
466 self.cwd = None # will hold the SConscript directory for target nodes
467 self.duplicate = directory.duplicate
470 """Completely clear a Node.FS.Base object of all its cached
471 state (so that it can be re-evaluated by interfaces that do
472 continuous integration builds).
475 SCons.Node.Node.clear(self)
480 def get_suffix(self):
482 return SCons.Util.splitext(self.name)[1]
488 """A Node.FS.Base object's string representation is its path
492 return self._save_str()
493 return self._get_str()
497 return self._get_str()
500 if self.duplicate or self.is_derived():
501 return self.get_path()
502 return self.srcnode().get_path()
508 return self.fs.exists(self.abspath)
512 return self.rfile().exists()
514 def is_under(self, dir):
518 return self.dir.is_under(dir)
524 """If this node is in a build path, return the node
525 corresponding to its source file. Otherwise, return
532 srcnode = self.fs.Entry(name, dir.srcdir,
533 klass=self.__class__)
534 if srcnode.is_under(dir):
535 # Shouldn't source from something in the build
536 # path: probably means build_dir is under
537 # src_dir and we are reflecting.
540 name = dir.name + os.sep + name
544 def get_path(self, dir=None):
545 """Return path relative to the current working directory of the
546 Node.FS.Base object that owns us."""
548 dir = self.fs.getcwd()
552 path_elems.append('.')
554 while d != dir and not isinstance(d, ParentOfRoot):
555 path_elems.append(d.name)
558 ret = string.join(path_elems, os.sep)
561 def set_src_builder(self, builder):
562 """Set the source code builder for this node."""
563 self.sbuilder = builder
564 if not self.has_builder():
565 self.builder_set(builder)
567 def src_builder(self):
568 """Fetch the source code builder for this node.
570 If there isn't one, we cache the source code builder specified
571 for the directory (which in turn will cache the value from its
572 parent directory, and so on up to the file system root).
576 except AttributeError:
577 scb = self.dir.src_builder()
581 def get_abspath(self):
582 """Get the absolute path of the file."""
585 def for_signature(self):
586 # Return just our name. Even an absolute path would not work,
587 # because that can change thanks to symlinks or remapped network
591 def get_subst_proxy(self):
594 except AttributeError:
595 ret = EntryProxy(self)
600 """This is the class for generic Node.FS entries--that is, things
601 that could be a File or a Dir, but we're just not sure yet.
602 Consequently, the methods in this class really exist just to
603 transform their associated object into the right class when the
604 time comes, and then call the same-named method in the transformed
608 """We're a generic Entry, but the caller is actually looking for
609 a File at this point, so morph into one."""
610 self.__class__ = File
613 return File.rfile(self)
615 def get_found_includes(self, env, scanner, target):
616 """If we're looking for included files, it's because this Entry
617 is really supposed to be a File itself."""
619 return node.get_found_includes(env, scanner, target)
621 def scanner_key(self):
622 return self.get_suffix()
624 def get_contents(self):
625 """Fetch the contents of the entry.
627 Since this should return the real contents from the file
628 system, we check to see into what sort of subclass we should
630 if self.fs.isfile(self.abspath):
631 self.__class__ = File
633 return self.get_contents()
634 if self.fs.isdir(self.abspath):
637 return self.get_contents()
638 if self.fs.islink(self.abspath):
639 return '' # avoid errors for dangling symlinks
643 """Return if the Entry exists. Check the file system to see
644 what we should turn into first. Assume a file if there's no
646 if self.fs.isdir(self.abspath):
649 return Dir.exists(self)
651 self.__class__ = File
654 return File.exists(self)
656 def calc_signature(self, calc=None):
657 """Return the Entry's calculated signature. Check the file
658 system to see what we should turn into first. Assume a file if
659 there's no directory."""
660 if self.fs.isdir(self.abspath):
663 return Dir.calc_signature(self, calc)
665 self.__class__ = File
668 return File.calc_signature(self, calc)
670 def must_be_a_Dir(self):
671 """Called to make sure a Node is a Dir. Since we're an
672 Entry, we can morph into one."""
676 # This is for later so we can differentiate between Entry the class and Entry
677 # the method of the FS class.
683 __metaclass__ = SCons.Memoize.Memoized_Metaclass
685 # This class implements an abstraction layer for operations involving
686 # a local file system. Essentially, this wraps any function in
687 # the os, os.path or shutil modules that we use to actually go do
688 # anything with or to the local file system.
690 # Note that there's a very good chance we'll refactor this part of
691 # the architecture in some way as we really implement the interface(s)
692 # for remote file system Nodes. For example, the right architecture
693 # might be to have this be a subclass instead of a base class.
694 # Nevertheless, we're using this as a first step in that direction.
696 # We're not using chdir() yet because the calling subclass method
697 # needs to use os.chdir() directly to avoid recursion. Will we
698 # really need this one?
699 #def chdir(self, path):
700 # return os.chdir(path)
701 def chmod(self, path, mode):
702 return os.chmod(path, mode)
703 def copy2(self, src, dst):
704 return shutil.copy2(src, dst)
705 def exists(self, path):
706 return os.path.exists(path)
707 def getmtime(self, path):
708 return os.path.getmtime(path)
709 def isdir(self, path):
710 return os.path.isdir(path)
711 def isfile(self, path):
712 return os.path.isfile(path)
713 def link(self, src, dst):
714 return os.link(src, dst)
715 def listdir(self, path):
716 return os.listdir(path)
717 def makedirs(self, path):
718 return os.makedirs(path)
719 def mkdir(self, path):
720 return os.mkdir(path)
721 def rename(self, old, new):
722 return os.rename(old, new)
723 def stat(self, path):
725 def symlink(self, src, dst):
726 return os.symlink(src, dst)
727 def open(self, path):
729 def unlink(self, path):
730 return os.unlink(path)
732 if hasattr(os, 'symlink'):
733 def islink(self, path):
734 return os.path.islink(path)
735 def exists_or_islink(self, path):
736 return os.path.exists(path) or os.path.islink(path)
738 def islink(self, path):
739 return 0 # no symlinks
740 exists_or_islink = exists
742 if not SCons.Memoize.has_metaclass:
744 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
745 def __init__(self, *args, **kw):
746 apply(_FSBase.__init__, (self,)+args, kw)
747 SCons.Memoize.Memoizer.__init__(self)
751 # # Skeleton for the obvious methods we might need from the
752 # # abstraction layer for a remote filesystem.
753 # def upload(self, local_src, remote_dst):
755 # def download(self, remote_src, local_dst):
761 def __init__(self, path = None):
762 """Initialize the Node.FS subsystem.
764 The supplied path is the top of the source tree, where we
765 expect to find the top-level build file. If no path is
766 supplied, the current directory is the default.
768 The path argument must be a valid absolute path.
770 if __debug__: logInstanceCreation(self)
773 self.pathTop = os.getcwd()
777 self.SConstruct_dir = None
778 self.CachePath = None
779 self.cache_force = None
780 self.cache_show = None
782 def set_toplevel_dir(self, path):
783 assert not self.Top, "You can only set the top-level path on an FS object that has not had its File, Dir, or Entry methods called yet."
786 def clear_cache(self):
790 def set_SConstruct_dir(self, dir):
791 self.SConstruct_dir = dir
793 def __setTopLevelDir(self):
795 self.Top = self.__doLookup(Dir, os.path.normpath(self.pathTop))
800 self.__setTopLevelDir()
803 def __checkClass(self, node, klass):
804 if isinstance(node, klass) or klass == Entry:
806 if node.__class__ == Entry:
807 node.__class__ = klass
810 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
811 (node.__class__.__name__, node.path, klass.__name__)
813 def __doLookup(self, fsclass, name, directory = None, create = 1):
814 """This method differs from the File and Dir factory methods in
815 one important way: the meaning of the directory parameter.
816 In this method, if directory is None or not supplied, the supplied
817 name is expected to be an absolute path. If you try to look up a
818 relative path with directory=None, then an AssertionError will be
823 # This is a stupid hack to compensate for the fact
824 # that the POSIX and Win32 versions of os.path.normpath()
825 # behave differently. In particular, in POSIX:
826 # os.path.normpath('./') == '.'
828 # os.path.normpath('./') == ''
829 # os.path.normpath('.\\') == ''
831 # This is a definite bug in the Python library, but we have
834 path_comp = string.split(name, os.sep)
835 drive, path_first = os.path.splitdrive(path_comp[0])
838 drive = _my_normcase(drive)
840 directory = self.Root[drive]
843 raise SCons.Errors.UserError
844 directory = RootDir(drive, ParentOfRoot(), self)
845 self.Root[drive] = directory
846 path_comp = path_comp[1:]
848 path_comp = [ path_first, ] + path_comp[1:]
853 # Lookup the directory
854 for path_name in path_comp[:-1]:
855 path_norm = _my_normcase(path_name)
857 d = directory.entries[path_norm]
860 raise SCons.Errors.UserError
862 # look at the actual filesystem and make sure there isn't
863 # a file already there
864 path = directory.entry_path(path_name)
865 if self.isfile(path):
867 "File %s found where directory expected." % path
869 dir_temp = Dir(path_name, directory, self)
870 directory.entries[path_norm] = dir_temp
871 directory.add_wkid(dir_temp)
877 entry_norm = _my_normcase(path_comp[-1])
879 e = directory.entries[entry_norm]
882 raise SCons.Errors.UserError
884 # make sure we don't create File nodes when there is actually
885 # a directory at that path on the disk, and vice versa
886 path = directory.entry_path(path_comp[-1])
890 "Directory %s found where file expected." % path
892 if self.isfile(path):
894 "File %s found where directory expected." % path
896 result = fsclass(path_comp[-1], directory, self)
897 directory.entries[entry_norm] = result
898 directory.add_wkid(result)
900 result = self.__checkClass(e, fsclass)
903 def __transformPath(self, name, directory):
904 """Take care of setting up the correct top-level directory,
905 usually in preparation for a call to doLookup().
907 If the path name is prepended with a '#', then it is unconditionally
908 interpreted as relative to the top-level directory of this FS.
910 If directory is None, and name is a relative path,
911 then the same applies.
913 self.__setTopLevelDir()
914 if name and name[0] == '#':
917 if name and (name[0] == os.sep or name[0] == '/'):
918 # Correct such that '#/foo' is equivalent
921 name = os.path.join('.', os.path.normpath(name))
923 directory = self._cwd
924 return (os.path.normpath(name), directory)
926 def chdir(self, dir, change_os_dir=0):
927 """Change the current working directory for lookups.
928 If change_os_dir is true, we will also change the "real" cwd
931 self.__setTopLevelDir()
937 os.chdir(dir.abspath)
942 def Entry(self, name, directory = None, create = 1, klass=None):
943 """Lookup or create a generic Entry node with the specified name.
944 If the name is a relative path (begins with ./, ../, or a file
945 name), then it is looked up relative to the supplied directory
946 node, or to the top level directory of the FS (supplied at
947 construction time) if no directory is supplied.
953 if isinstance(name, Base):
954 return self.__checkClass(name, klass)
956 if directory and not isinstance(directory, Dir):
957 directory = self.Dir(directory)
958 name, directory = self.__transformPath(name, directory)
959 return self.__doLookup(klass, name, directory, create)
961 def File(self, name, directory = None, create = 1):
962 """Lookup or create a File node with the specified name. If
963 the name is a relative path (begins with ./, ../, or a file name),
964 then it is looked up relative to the supplied directory node,
965 or to the top level directory of the FS (supplied at construction
966 time) if no directory is supplied.
968 This method will raise TypeError if a directory is found at the
972 return self.Entry(name, directory, create, File)
974 def Dir(self, name, directory = None, create = 1):
975 """Lookup or create a Dir node with the specified name. If
976 the name is a relative path (begins with ./, ../, or a file name),
977 then it is looked up relative to the supplied directory node,
978 or to the top level directory of the FS (supplied at construction
979 time) if no directory is supplied.
981 This method will raise TypeError if a normal file is found at the
985 return self.Entry(name, directory, create, Dir)
987 def BuildDir(self, build_dir, src_dir, duplicate=1):
988 """Link the supplied build directory to the source directory
989 for purposes of building files."""
991 self.__setTopLevelDir()
992 if not isinstance(src_dir, SCons.Node.Node):
993 src_dir = self.Dir(src_dir)
994 if not isinstance(build_dir, SCons.Node.Node):
995 build_dir = self.Dir(build_dir)
996 if not src_dir.is_under(self.Top):
997 raise SCons.Errors.UserError, "Source directory must be under top of build tree."
998 if src_dir.is_under(build_dir):
999 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
1000 if build_dir.srcdir:
1001 if build_dir.srcdir == src_dir:
1002 return # We already did this.
1003 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
1004 build_dir.link(src_dir, duplicate)
1006 def Repository(self, *dirs):
1007 """Specify Repository directories to search."""
1009 if not isinstance(d, SCons.Node.Node):
1011 self.__setTopLevelDir()
1012 self.Top.addRepository(d)
1014 def Rsearch(self, path, clazz=_classEntry, cwd=None):
1015 """Search for something in a Repository. Returns the first
1016 one found in the list, or None if there isn't one.
1019 if isinstance(path, SCons.Node.Node):
1022 name, d = self.__transformPath(path, cwd)
1023 n = self.__doLookup(clazz, name, d)
1026 if isinstance(n, Dir):
1027 # If n is a Directory that has Repositories directly
1028 # attached to it, then any of those is a valid Repository
1029 # path. Return the first one that exists.
1030 reps = filter(lambda x: x.exists(), n.getRepositories())
1035 # Search repositories of all directories that this file is under.
1037 for rep in d.getRepositories():
1039 rnode = self.__doLookup(clazz, name, rep)
1040 # Only find the node if it exists and it is not
1041 # a derived file. If for some reason, we are
1042 # explicitly building a file IN a Repository, we
1043 # don't want it to show up in the build tree.
1044 # This is usually the case with BuildDir().
1045 # We only want to find pre-existing files.
1046 if rnode.exists() and \
1047 (isinstance(rnode, Dir) or not rnode.is_derived()):
1050 pass # Wrong type of node.
1051 # Prepend directory name
1052 name = d.name + os.sep + name
1053 # Go up one directory
1057 def Rsearchall(self, pathlist, must_exist=1, clazz=_classEntry, cwd=None):
1058 """Search for a list of somethings in the Repository list.
1062 if SCons.Util.is_String(pathlist):
1063 pathlist = string.split(pathlist, os.pathsep)
1064 if not SCons.Util.is_List(pathlist):
1065 pathlist = [pathlist]
1066 for path in filter(None, pathlist):
1067 if isinstance(path, SCons.Node.Node):
1070 name, d = self.__transformPath(path, cwd)
1071 n = self.__doLookup(clazz, name, d)
1072 if not must_exist or n.exists():
1074 if isinstance(n, Dir):
1075 # If this node is a directory, then any repositories
1076 # attached to this node can be repository paths.
1077 ret.extend(filter(lambda x, me=must_exist, clazz=clazz: isinstance(x, clazz) and (not me or x.exists()),
1078 n.getRepositories()))
1082 # Search repositories of all directories that this file
1085 for rep in d.getRepositories():
1087 rnode = self.__doLookup(clazz, name, rep)
1088 # Only find the node if it exists (or
1089 # must_exist is zero) and it is not a
1090 # derived file. If for some reason, we
1091 # are explicitly building a file IN a
1092 # Repository, we don't want it to show up in
1093 # the build tree. This is usually the case
1094 # with BuildDir(). We only want to find
1095 # pre-existing files.
1096 if (not must_exist or rnode.exists()) and \
1097 (not rnode.is_derived() or isinstance(rnode, Dir)):
1100 pass # Wrong type of node.
1101 # Prepend directory name
1102 name = d.name + os.sep + name
1103 # Go up one directory
1107 def CacheDir(self, path):
1108 self.CachePath = path
1110 def build_dir_target_climb(self, orig, dir, tail):
1111 """Create targets in corresponding build directories
1113 Climb the directory tree, and look up path names
1114 relative to any linked build directories we find.
1119 fmt = "building associated BuildDir targets: %s"
1122 for bd in dir.build_dirs:
1123 if start_dir.is_under(bd):
1124 # If already in the build-dir location, don't reflect
1125 return [orig], fmt % str(orig)
1126 p = apply(os.path.join, [bd.path] + tail)
1127 targets.append(self.Entry(p))
1128 tail = [dir.name] + tail
1131 message = fmt % string.join(map(str, targets))
1132 return targets, message
1136 """A class for directories in a file system.
1139 def __init__(self, name, directory, fs):
1140 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1141 Base.__init__(self, name, directory, fs)
1145 """Turn a file system Node (either a freshly initialized directory
1146 object or a separate Entry object) into a proper directory object.
1148 Set up this directory's entries and hook it into the file
1149 system tree. Specify that directories (this Node) don't use
1150 signatures for calculating whether they're current.
1153 self.repositories = []
1157 self.entries['.'] = self
1158 self.entries['..'] = self.dir
1160 self.builder = get_MkdirBuilder()
1162 self._sconsign = None
1163 self.build_dirs = []
1165 def __clearRepositoryCache(self, duplicate=None):
1166 """Called when we change the repository(ies) for a directory.
1167 This clears any cached information that is invalidated by changing
1170 for node in self.entries.values():
1171 if node != self.dir:
1172 if node != self and isinstance(node, Dir):
1173 node.__clearRepositoryCache(duplicate)
1178 except AttributeError:
1180 if duplicate != None:
1181 node.duplicate=duplicate
1183 def __resetDuplicate(self, node):
1185 node.duplicate = node.get_dir().duplicate
1187 def Entry(self, name):
1188 """Create an entry node named 'name' relative to this directory."""
1189 return self.fs.Entry(name, self)
1191 def Dir(self, name):
1192 """Create a directory node named 'name' relative to this directory."""
1193 return self.fs.Dir(name, self)
1195 def File(self, name):
1196 """Create a file node named 'name' relative to this directory."""
1197 return self.fs.File(name, self)
1199 def link(self, srcdir, duplicate):
1200 """Set this directory as the build directory for the
1201 supplied source directory."""
1202 self.srcdir = srcdir
1203 self.duplicate = duplicate
1204 self.__clearRepositoryCache(duplicate)
1205 srcdir.build_dirs.append(self)
1207 def getRepositories(self):
1208 """Returns a list of repositories for this directory."""
1209 if self.srcdir and not self.duplicate:
1211 return self._srcreps
1212 except AttributeError:
1213 self._srcreps = self.fs.Rsearchall(self.srcdir.path,
1218 return self._srcreps
1219 return self.repositories
1221 def addRepository(self, dir):
1222 if not dir in self.repositories and dir != self:
1223 self.repositories.append(dir)
1224 self.__clearRepositoryCache()
1227 return self.entries['..']
1230 if not self.entries['..']:
1233 return self.entries['..'].root()
1236 if not self.implicit is None:
1239 self.implicit_dict = {}
1240 self._children_reset()
1242 for filename in self.fs.listdir(self.abspath):
1243 if filename != '.sconsign':
1244 self.Entry(filename)
1246 # Directory does not exist. No big deal
1248 keys = filter(lambda k: k != '.' and k != '..', self.entries.keys())
1249 kids = map(lambda x, s=self: s.entries[x], keys)
1251 return cmp(one.abspath, two.abspath)
1253 self._add_child(self.implicit, self.implicit_dict, kids)
1255 def build(self, **kw):
1256 """A null "builder" for directories."""
1258 if not self.builder is MkdirBuilder:
1259 apply(SCons.Node.Node.build, [self,], kw)
1261 def multiple_side_effect_has_builder(self):
1263 return not self.builder is MkdirBuilder and self.has_builder()
1265 def alter_targets(self):
1266 """Return any corresponding targets in a build directory.
1268 return self.fs.build_dir_target_climb(self, self, [])
1270 def scanner_key(self):
1271 """A directory does not get scanned."""
1274 def get_contents(self):
1275 """Return aggregate contents of all our children."""
1276 contents = cStringIO.StringIO()
1277 for kid in self.children():
1278 contents.write(kid.get_contents())
1279 return contents.getvalue()
1284 def current(self, calc=None):
1285 """If all of our children were up-to-date, then this
1286 directory was up-to-date, too."""
1287 if not self.builder is MkdirBuilder and not self.exists():
1290 for kid in self.children():
1292 if s and (not state or s > state):
1295 if state == 0 or state == SCons.Node.up_to_date:
1303 if not self.exists():
1304 n = self.fs.Rsearch(self.path, clazz=Dir, cwd=self.fs.Top)
1310 """Return the .sconsign file info for this directory,
1311 creating it first if necessary."""
1312 if not self._sconsign:
1313 import SCons.SConsign
1314 self._sconsign = SCons.SConsign.ForDirectory(self)
1315 return self._sconsign
1318 """Dir has a special need for srcnode()...if we
1319 have a srcdir attribute set, then that *is* our srcnode."""
1322 return Base.srcnode(self)
1324 def get_timestamp(self):
1325 """Return the latest timestamp from among our children"""
1327 for kid in self.children():
1328 if kid.get_timestamp() > stamp:
1329 stamp = kid.get_timestamp()
1332 def entry_abspath(self, name):
1333 return self.abspath + os.sep + name
1335 def entry_path(self, name):
1336 return self.path + os.sep + name
1338 def must_be_a_Dir(self):
1339 """Called to make sure a Node is a Dir. Since we're already
1340 one, this is a no-op for us."""
1344 """A class for the root directory of a file system.
1346 This is the same as a Dir class, except that the path separator
1347 ('/' or '\\') is actually part of the name, so we don't need to
1348 add a separator when creating the path names of entries within
1351 def __init__(self, name, directory, fs):
1352 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1353 Base.__init__(self, name, directory, fs)
1354 self.path = self.path + os.sep
1355 self.abspath = self.abspath + os.sep
1358 def entry_abspath(self, name):
1359 return self.abspath + name
1361 def entry_path(self, name):
1362 return self.path + name
1366 def __cmp__(self, other):
1368 return cmp(self.bsig, other.bsig)
1369 except AttributeError:
1373 """A class for files in a file system.
1375 def __init__(self, name, directory, fs):
1376 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1377 Base.__init__(self, name, directory, fs)
1380 def Entry(self, name):
1381 """Create an entry node named 'name' relative to
1382 the SConscript directory of this file."""
1383 return self.fs.Entry(name, self.cwd)
1385 def Dir(self, name):
1386 """Create a directory node named 'name' relative to
1387 the SConscript directory of this file."""
1388 return self.fs.Dir(name, self.cwd)
1390 def File(self, name):
1391 """Create a file node named 'name' relative to
1392 the SConscript directory of this file."""
1393 return self.fs.File(name, self.cwd)
1395 def RDirs(self, pathlist):
1396 """Search for a list of directories in the Repository list."""
1397 return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
1401 """Turn a file system node into a File object. __cache_reset__"""
1402 self.scanner_paths = {}
1403 if not hasattr(self, '_local'):
1407 return self.dir.root()
1409 def scanner_key(self):
1410 return self.get_suffix()
1412 def get_contents(self):
1413 if not self.rexists():
1415 return open(self.rfile().abspath, "rb").read()
1417 def get_timestamp(self):
1419 return self.fs.getmtime(self.rfile().abspath)
1423 def store_info(self, obj):
1424 # Merge our build information into the already-stored entry.
1425 # This accomodates "chained builds" where a file that's a target
1426 # in one build (SConstruct file) is a source in a different build.
1427 # See test/chained-build.py for the use case.
1428 entry = self.get_stored_info()
1429 for key, val in obj.__dict__.items():
1430 entry.__dict__[key] = val
1431 self.dir.sconsign().set_entry(self.name, entry)
1433 def get_stored_info(self):
1436 stored = self.dir.sconsign().get_entry(self.name)
1437 except (KeyError, OSError):
1440 if isinstance(stored, BuildInfo):
1442 # The stored build information isn't a BuildInfo object.
1443 # This probably means it's an old SConsignEntry from SCons
1444 # 0.95 or before. The relevant attribute names are the same,
1445 # though, so just copy the attributes over to an object of
1448 for key, val in stored.__dict__.items():
1449 setattr(binfo, key, val)
1452 def get_stored_implicit(self):
1453 binfo = self.get_stored_info()
1455 return binfo.bimplicit
1456 except AttributeError:
1459 def get_found_includes(self, env, scanner, target):
1460 """Return the included implicit dependencies in this file.
1461 Cache results so we only scan the file once regardless of
1462 how many times this information is requested."""
1467 path = target.scanner_paths[scanner]
1468 except AttributeError:
1469 # The target had no scanner_paths attribute, which means
1470 # it's an Alias or some other node that's not actually a
1471 # file. In that case, back off and use the path for this
1474 path = self.scanner_paths[scanner]
1476 path = scanner.path(env, self.cwd, target)
1477 self.scanner_paths[scanner] = path
1479 path = scanner.path(env, target.cwd, target)
1480 target.scanner_paths[scanner] = path
1482 return scanner(self, env, path)
1484 def _createDir(self):
1485 # ensure that the directories for this node are
1493 listDirs.append(parent)
1495 if isinstance(p, ParentOfRoot):
1496 raise SCons.Errors.StopError, parent.path
1499 for dirnode in listDirs:
1501 # Don't call dirnode.build(), call the base Node method
1502 # directly because we definitely *must* create this
1503 # directory. The dirnode.build() method will suppress
1504 # the build if it's the default builder.
1505 SCons.Node.Node.build(dirnode)
1506 dirnode.get_executor().nullify()
1507 # The build() action may or may not have actually
1508 # created the directory, depending on whether the -n
1509 # option was used or not. Delete the _exists and
1510 # _rexists attributes so they can be reevaluated.
1515 def retrieve_from_cache(self):
1516 """Try to retrieve the node's content from a cache
1518 This method is called from multiple threads in a parallel build,
1519 so only do thread safe stuff here. Do thread unsafe stuff in
1522 Note that there's a special trick here with the execute flag
1523 (one that's not normally done for other actions). Basically
1524 if the user requested a noexec (-n) build, then
1525 SCons.Action.execute_actions is set to 0 and when any action
1526 is called, it does its showing but then just returns zero
1527 instead of actually calling the action execution operation.
1528 The problem for caching is that if the file does NOT exist in
1529 cache then the CacheRetrieveString won't return anything to
1530 show for the task, but the Action.__call__ won't call
1531 CacheRetrieveFunc; instead it just returns zero, which makes
1532 the code below think that the file *was* successfully
1533 retrieved from the cache, therefore it doesn't do any
1534 subsequent building. However, the CacheRetrieveString didn't
1535 print anything because it didn't actually exist in the cache,
1536 and no more build actions will be performed, so the user just
1537 sees nothing. The fix is to tell Action.__call__ to always
1538 execute the CacheRetrieveFunc and then have the latter
1539 explicitly check SCons.Action.execute_actions itself.
1541 Returns true iff the node was successfully retrieved.
1543 b = self.is_derived()
1544 if not b and not self.has_src_builder():
1546 if b and self.fs.CachePath:
1547 if self.fs.cache_show:
1548 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1549 self.build(presub=0, execute=0)
1551 elif CacheRetrieve(self, [], None, execute=1) == 0:
1556 """Called just after this node is successfully built.
1558 # Push this file out to cache before the superclass Node.built()
1559 # method has a chance to clear the build signature, which it
1560 # will do if this file has a source scanner.
1561 if self.fs.CachePath and self.fs.exists(self.path):
1562 CachePush(self, [], None)
1563 self.fs.clear_cache()
1564 SCons.Node.Node.built(self)
1567 if self.fs.CachePath and self.fs.cache_force and self.fs.exists(self.path):
1568 CachePush(self, None, None)
1570 def has_src_builder(self):
1571 """Return whether this Node has a source builder or not.
1573 If this Node doesn't have an explicit source code builder, this
1574 is where we figure out, on the fly, if there's a transparent
1575 source code builder for it.
1577 Note that if we found a source builder, we also set the
1578 self.builder attribute, so that all of the methods that actually
1579 *build* this file don't have to do anything different.
1583 except AttributeError:
1587 scb = self.dir.src_builder()
1591 sccspath = os.path.join('SCCS', 's.' + self.name)
1593 sccspath = os.path.join(dir, sccspath)
1594 if self.fs.exists(sccspath):
1595 scb = get_DefaultSCCSBuilder()
1597 rcspath = os.path.join('RCS', self.name + ',v')
1599 rcspath = os.path.join(dir, rcspath)
1600 if os.path.exists(rcspath):
1601 scb = get_DefaultRCSBuilder()
1603 self.builder_set(scb)
1605 return not scb is None
1607 def alter_targets(self):
1608 """Return any corresponding targets in a build directory.
1610 if self.is_derived():
1612 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1614 def is_pseudo_derived(self):
1616 return self.has_src_builder()
1618 def _rmv_existing(self):
1620 Unlink(self, [], None)
1623 """Prepare for this file to be created."""
1624 SCons.Node.Node.prepare(self)
1626 if self.get_state() != SCons.Node.up_to_date:
1628 if self.is_derived() and not self.precious:
1629 self._rmv_existing()
1633 except SCons.Errors.StopError, drive:
1634 desc = "No drive `%s' for target `%s'." % (drive, self)
1635 raise SCons.Errors.StopError, desc
1638 """Remove this file."""
1639 if self.fs.exists_or_islink(self.path):
1640 self.fs.unlink(self.path)
1646 # Duplicate from source path if we are set up to do this.
1647 if self.duplicate and not self.is_derived() and not self.linked:
1650 return Base.exists(self)
1652 if src.abspath != self.abspath and src.exists():
1655 Unlink(self, None, None)
1656 except SCons.Errors.BuildError:
1659 Link(self, src, None)
1660 except SCons.Errors.BuildError, e:
1661 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1662 raise SCons.Errors.StopError, desc
1664 # The Link() action may or may not have actually
1665 # created the file, depending on whether the -n
1666 # option was used or not. Delete the _exists and
1667 # _rexists attributes so they can be reevaluated.
1669 return Base.exists(self)
1671 def new_binfo(self):
1674 def del_cinfo(self):
1677 except AttributeError:
1680 del self.binfo.timestamp
1681 except AttributeError:
1684 def calc_csig(self, calc=None):
1686 Generate a node's content signature, the digested signature
1690 cache - alternate node to use for the signature cache
1691 returns - the content signature
1694 calc = self.calculator()
1697 return self.binfo.csig
1698 except AttributeError:
1701 if calc.max_drift >= 0:
1702 old = self.get_stored_info()
1707 mtime = self.get_timestamp()
1710 raise SCons.Errors.UserError, "no such %s" % self
1713 if (old.timestamp and old.csig and old.timestamp == mtime):
1714 # use the signature stored in the .sconsign file
1717 csig = calc.module.signature(self)
1718 except AttributeError:
1719 csig = calc.module.signature(self)
1721 if calc.max_drift >= 0 and (time.time() - mtime) > calc.max_drift:
1724 except AttributeError:
1725 binfo = self.binfo = self.new_binfo()
1727 binfo.timestamp = mtime
1728 self.store_info(binfo)
1732 def current(self, calc=None):
1733 self.binfo = self.gen_binfo(calc)
1737 if self.always_build:
1739 if not self.exists():
1740 # The file doesn't exist locally...
1743 # ...but there is one in a Repository...
1744 old = r.get_stored_info()
1745 if old == self.binfo:
1746 # ...and it's even up-to-date...
1748 # ...and they'd like a local copy.
1749 LocalCopy(self, r, None)
1750 self.store_info(self.binfo)
1754 old = self.get_stored_info()
1755 return (old == self.binfo)
1760 if not self.exists():
1761 n = self.fs.Rsearch(self.path, clazz=File,
1768 return str(self.rfile())
1770 def cachepath(self):
1771 if not self.fs.CachePath:
1773 if self.binfo.bsig is None:
1774 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1775 # Add the path to the cache signature, because multiple
1776 # targets built by the same action will all have the same
1777 # build signature, and we have to differentiate them somehow.
1778 cache_sig = SCons.Sig.MD5.collect([self.binfo.bsig, self.path])
1779 subdir = string.upper(cache_sig[0])
1780 dir = os.path.join(self.fs.CachePath, subdir)
1781 return dir, os.path.join(dir, cache_sig)
1783 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1784 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1786 def must_be_a_Dir(self):
1787 """Called to make sure a Node is a Dir. Since we're already a
1788 File, this is a TypeError..."""
1789 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1793 def find_file(filename, paths, node_factory=default_fs.File, verbose=None):
1795 find_file(str, [Dir()]) -> [nodes]
1797 filename - a filename to find
1798 paths - a list of directory path *nodes* to search in. Can be
1799 represented as a list, a tuple, or a callable that is
1800 called with no arguments and returns the list or tuple.
1802 returns - the node created from the found file.
1804 Find a node corresponding to either a derived file or a file
1805 that exists already.
1807 Only the first file found is returned, and none is returned
1808 if no file is found.
1810 if verbose and not SCons.Util.is_String(verbose):
1811 verbose = "find_file"
1819 sys.stdout.write(" %s: looking for '%s' in '%s' ...\n" % (verbose, filename, dir))
1821 node = node_factory(filename, dir)
1822 # Return true if the node exists or is a derived node.
1823 if node.is_derived() or \
1824 node.is_pseudo_derived() or \
1825 (isinstance(node, SCons.Node.FS.Base) and node.exists()):
1828 sys.stdout.write(" %s: ... FOUND '%s' in '%s'\n" % (verbose, filename, dir))
1831 # If we find a directory instead of a file, we don't care
1836 def find_files(filenames, paths, node_factory = default_fs.File):
1838 find_files([str], [Dir()]) -> [nodes]
1840 filenames - a list of filenames to find
1841 paths - a list of directory path *nodes* to search in
1843 returns - the nodes created from the found files.
1845 Finds nodes corresponding to either derived files or files
1848 Only the first file found is returned for each filename,
1849 and any files that aren't found are ignored.
1851 nodes = map(lambda x, paths=paths, node_factory=node_factory:
1852 find_file(x, paths, node_factory),
1854 return filter(lambda x: x != None, nodes)