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 target[0].fs.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, path):
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, path)
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."""
677 # This is for later so we can differentiate between Entry the class and Entry
678 # the method of the FS class.
684 __metaclass__ = SCons.Memoize.Memoized_Metaclass
686 # This class implements an abstraction layer for operations involving
687 # a local file system. Essentially, this wraps any function in
688 # the os, os.path or shutil modules that we use to actually go do
689 # anything with or to the local file system.
691 # Note that there's a very good chance we'll refactor this part of
692 # the architecture in some way as we really implement the interface(s)
693 # for remote file system Nodes. For example, the right architecture
694 # might be to have this be a subclass instead of a base class.
695 # Nevertheless, we're using this as a first step in that direction.
697 # We're not using chdir() yet because the calling subclass method
698 # needs to use os.chdir() directly to avoid recursion. Will we
699 # really need this one?
700 #def chdir(self, path):
701 # return os.chdir(path)
702 def chmod(self, path, mode):
703 return os.chmod(path, mode)
704 def copy2(self, src, dst):
705 return shutil.copy2(src, dst)
706 def exists(self, path):
707 return os.path.exists(path)
708 def getmtime(self, path):
709 return os.path.getmtime(path)
710 def isdir(self, path):
711 return os.path.isdir(path)
712 def isfile(self, path):
713 return os.path.isfile(path)
714 def link(self, src, dst):
715 return os.link(src, dst)
716 def listdir(self, path):
717 return os.listdir(path)
718 def makedirs(self, path):
719 return os.makedirs(path)
720 def mkdir(self, path):
721 return os.mkdir(path)
722 def rename(self, old, new):
723 return os.rename(old, new)
724 def stat(self, path):
726 def symlink(self, src, dst):
727 return os.symlink(src, dst)
728 def open(self, path):
730 def unlink(self, path):
731 return os.unlink(path)
733 if hasattr(os, 'symlink'):
734 def islink(self, path):
735 return os.path.islink(path)
736 def exists_or_islink(self, path):
737 return os.path.exists(path) or os.path.islink(path)
739 def islink(self, path):
740 return 0 # no symlinks
741 exists_or_islink = exists
743 if not SCons.Memoize.has_metaclass:
745 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
746 def __init__(self, *args, **kw):
747 apply(_FSBase.__init__, (self,)+args, kw)
748 SCons.Memoize.Memoizer.__init__(self)
752 # # Skeleton for the obvious methods we might need from the
753 # # abstraction layer for a remote filesystem.
754 # def upload(self, local_src, remote_dst):
756 # def download(self, remote_src, local_dst):
762 def __init__(self, path = None):
763 """Initialize the Node.FS subsystem.
765 The supplied path is the top of the source tree, where we
766 expect to find the top-level build file. If no path is
767 supplied, the current directory is the default.
769 The path argument must be a valid absolute path.
771 if __debug__: logInstanceCreation(self)
774 self.pathTop = os.getcwd()
778 self.SConstruct_dir = None
779 self.CachePath = None
780 self.cache_force = None
781 self.cache_show = None
783 def set_toplevel_dir(self, path):
784 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."
787 def clear_cache(self):
791 def set_SConstruct_dir(self, dir):
792 self.SConstruct_dir = dir
794 def __setTopLevelDir(self):
796 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
801 self.__setTopLevelDir()
804 def __checkClass(self, node, klass):
805 if isinstance(node, klass) or klass == Entry:
807 if node.__class__ == Entry:
808 node.__class__ = klass
811 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
812 (node.__class__.__name__, node.path, klass.__name__)
814 def _doLookup(self, fsclass, name, directory = None, create = 1):
815 """This method differs from the File and Dir factory methods in
816 one important way: the meaning of the directory parameter.
817 In this method, if directory is None or not supplied, the supplied
818 name is expected to be an absolute path. If you try to look up a
819 relative path with directory=None, then an AssertionError will be
824 # This is a stupid hack to compensate for the fact
825 # that the POSIX and Win32 versions of os.path.normpath()
826 # behave differently. In particular, in POSIX:
827 # os.path.normpath('./') == '.'
829 # os.path.normpath('./') == ''
830 # os.path.normpath('.\\') == ''
832 # This is a definite bug in the Python library, but we have
835 path_comp = string.split(name, os.sep)
836 drive, path_first = os.path.splitdrive(path_comp[0])
839 drive = _my_normcase(drive)
841 directory = self.Root[drive]
844 raise SCons.Errors.UserError
845 directory = RootDir(drive, ParentOfRoot(), self)
846 self.Root[drive] = directory
847 path_comp = path_comp[1:]
849 path_comp = [ path_first, ] + path_comp[1:]
854 # Lookup the directory
855 for path_name in path_comp[:-1]:
856 path_norm = _my_normcase(path_name)
858 d = directory.entries[path_norm]
861 raise SCons.Errors.UserError
863 # look at the actual filesystem and make sure there isn't
864 # a file already there
865 path = directory.entry_path(path_name)
866 if self.isfile(path):
868 "File %s found where directory expected." % path
870 dir_temp = Dir(path_name, directory, self)
871 directory.entries[path_norm] = dir_temp
872 directory.add_wkid(dir_temp)
875 directory = d.must_be_a_Dir()
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 do_duplicate(self, src):
1287 def current(self, calc=None):
1288 """If all of our children were up-to-date, then this
1289 directory was up-to-date, too."""
1290 if not self.builder is MkdirBuilder and not self.exists():
1293 for kid in self.children():
1295 if s and (not state or s > state):
1298 if state == 0 or state == SCons.Node.up_to_date:
1306 if not self.exists():
1307 n = self.fs.Rsearch(self.path, clazz=Dir, cwd=self.fs.Top)
1313 """Return the .sconsign file info for this directory,
1314 creating it first if necessary."""
1315 if not self._sconsign:
1316 import SCons.SConsign
1317 self._sconsign = SCons.SConsign.ForDirectory(self)
1318 return self._sconsign
1321 """Dir has a special need for srcnode()...if we
1322 have a srcdir attribute set, then that *is* our srcnode."""
1325 return Base.srcnode(self)
1327 def get_timestamp(self):
1328 """Return the latest timestamp from among our children"""
1330 for kid in self.children():
1331 if kid.get_timestamp() > stamp:
1332 stamp = kid.get_timestamp()
1335 def entry_abspath(self, name):
1336 return self.abspath + os.sep + name
1338 def entry_path(self, name):
1339 return self.path + os.sep + name
1341 def must_be_a_Dir(self):
1342 """Called to make sure a Node is a Dir. Since we're already
1343 one, this is a no-op for us."""
1346 def entry_exists_on_disk(self, name):
1348 return self.fs.exists(self.entry_abspath(name))
1350 def rcs_on_disk(self, name):
1351 rcspath = 'RCS' + os.sep + name+',v'
1352 return self.entry_exists_on_disk(rcspath)
1354 def sccs_on_disk(self, name):
1355 sccspath = 'SCCS' + os.sep + 's.'+name
1356 return self.entry_exists_on_disk(sccspath)
1359 """A class for the root directory of a file system.
1361 This is the same as a Dir class, except that the path separator
1362 ('/' or '\\') is actually part of the name, so we don't need to
1363 add a separator when creating the path names of entries within
1366 def __init__(self, name, directory, fs):
1367 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1368 Base.__init__(self, name, directory, fs)
1369 self.path = self.path + os.sep
1370 self.abspath = self.abspath + os.sep
1373 def entry_abspath(self, name):
1374 return self.abspath + name
1376 def entry_path(self, name):
1377 return self.path + name
1381 def __cmp__(self, other):
1383 return cmp(self.bsig, other.bsig)
1384 except AttributeError:
1388 """A class for files in a file system.
1390 def __init__(self, name, directory, fs):
1391 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1392 Base.__init__(self, name, directory, fs)
1395 def Entry(self, name):
1396 """Create an entry node named 'name' relative to
1397 the SConscript directory of this file."""
1398 return self.fs.Entry(name, self.cwd)
1400 def Dir(self, name):
1401 """Create a directory node named 'name' relative to
1402 the SConscript directory of this file."""
1403 return self.fs.Dir(name, self.cwd)
1405 def File(self, name):
1406 """Create a file node named 'name' relative to
1407 the SConscript directory of this file."""
1408 return self.fs.File(name, self.cwd)
1410 def RDirs(self, pathlist):
1411 """Search for a list of directories in the Repository list."""
1412 return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
1416 """Turn a file system node into a File object. __cache_reset__"""
1417 self.scanner_paths = {}
1418 if not hasattr(self, '_local'):
1422 return self.dir.root()
1424 def scanner_key(self):
1425 return self.get_suffix()
1427 def get_contents(self):
1428 if not self.rexists():
1430 return open(self.rfile().abspath, "rb").read()
1432 def get_timestamp(self):
1434 return self.fs.getmtime(self.rfile().abspath)
1438 def store_info(self, obj):
1439 # Merge our build information into the already-stored entry.
1440 # This accomodates "chained builds" where a file that's a target
1441 # in one build (SConstruct file) is a source in a different build.
1442 # See test/chained-build.py for the use case.
1443 entry = self.get_stored_info()
1444 for key, val in obj.__dict__.items():
1445 entry.__dict__[key] = val
1446 self.dir.sconsign().set_entry(self.name, entry)
1448 def get_stored_info(self):
1451 stored = self.dir.sconsign().get_entry(self.name)
1452 except (KeyError, OSError):
1455 if isinstance(stored, BuildInfo):
1457 # The stored build information isn't a BuildInfo object.
1458 # This probably means it's an old SConsignEntry from SCons
1459 # 0.95 or before. The relevant attribute names are the same,
1460 # though, so just copy the attributes over to an object of
1463 for key, val in stored.__dict__.items():
1464 setattr(binfo, key, val)
1467 def get_stored_implicit(self):
1468 binfo = self.get_stored_info()
1470 return binfo.bimplicit
1471 except AttributeError:
1474 def get_found_includes(self, env, scanner, path):
1475 """Return the included implicit dependencies in this file.
1476 Cache results so we only scan the file once per path
1477 regardless of how many times this information is requested.
1481 return scanner(self, env, path)
1483 def _createDir(self):
1484 # ensure that the directories for this node are
1492 listDirs.append(parent)
1494 if isinstance(p, ParentOfRoot):
1495 raise SCons.Errors.StopError, parent.path
1498 for dirnode in listDirs:
1500 # Don't call dirnode.build(), call the base Node method
1501 # directly because we definitely *must* create this
1502 # directory. The dirnode.build() method will suppress
1503 # the build if it's the default builder.
1504 SCons.Node.Node.build(dirnode)
1505 dirnode.get_executor().nullify()
1506 # The build() action may or may not have actually
1507 # created the directory, depending on whether the -n
1508 # option was used or not. Delete the _exists and
1509 # _rexists attributes so they can be reevaluated.
1514 def retrieve_from_cache(self):
1515 """Try to retrieve the node's content from a cache
1517 This method is called from multiple threads in a parallel build,
1518 so only do thread safe stuff here. Do thread unsafe stuff in
1521 Note that there's a special trick here with the execute flag
1522 (one that's not normally done for other actions). Basically
1523 if the user requested a noexec (-n) build, then
1524 SCons.Action.execute_actions is set to 0 and when any action
1525 is called, it does its showing but then just returns zero
1526 instead of actually calling the action execution operation.
1527 The problem for caching is that if the file does NOT exist in
1528 cache then the CacheRetrieveString won't return anything to
1529 show for the task, but the Action.__call__ won't call
1530 CacheRetrieveFunc; instead it just returns zero, which makes
1531 the code below think that the file *was* successfully
1532 retrieved from the cache, therefore it doesn't do any
1533 subsequent building. However, the CacheRetrieveString didn't
1534 print anything because it didn't actually exist in the cache,
1535 and no more build actions will be performed, so the user just
1536 sees nothing. The fix is to tell Action.__call__ to always
1537 execute the CacheRetrieveFunc and then have the latter
1538 explicitly check SCons.Action.execute_actions itself.
1540 Returns true iff the node was successfully retrieved.
1542 b = self.is_derived()
1543 if not b and not self.has_src_builder():
1545 if b and self.fs.CachePath:
1546 if self.fs.cache_show:
1547 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1548 self.build(presub=0, execute=0)
1550 elif CacheRetrieve(self, [], None, execute=1) == 0:
1555 """Called just after this node is successfully built.
1557 # Push this file out to cache before the superclass Node.built()
1558 # method has a chance to clear the build signature, which it
1559 # will do if this file has a source scanner.
1560 if self.fs.CachePath and self.fs.exists(self.path):
1561 CachePush(self, [], None)
1562 self.fs.clear_cache()
1563 SCons.Node.Node.built(self)
1566 if self.fs.CachePath and self.fs.cache_force and self.fs.exists(self.path):
1567 CachePush(self, None, None)
1569 def has_src_builder(self):
1570 """Return whether this Node has a source builder or not.
1572 If this Node doesn't have an explicit source code builder, this
1573 is where we figure out, on the fly, if there's a transparent
1574 source code builder for it.
1576 Note that if we found a source builder, we also set the
1577 self.builder attribute, so that all of the methods that actually
1578 *build* this file don't have to do anything different.
1582 except AttributeError:
1586 scb = self.dir.src_builder()
1588 if self.dir.sccs_on_disk(self.name):
1589 scb = get_DefaultSCCSBuilder()
1590 elif self.dir.rcs_on_disk(self.name):
1591 scb = get_DefaultRCSBuilder()
1595 self.builder_set(scb)
1597 return not scb is None
1599 def alter_targets(self):
1600 """Return any corresponding targets in a build directory.
1602 if self.is_derived():
1604 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1606 def is_pseudo_derived(self):
1608 return self.has_src_builder()
1610 def _rmv_existing(self):
1612 Unlink(self, [], None)
1615 """Prepare for this file to be created."""
1616 SCons.Node.Node.prepare(self)
1618 if self.get_state() != SCons.Node.up_to_date:
1620 if self.is_derived() and not self.precious:
1621 self._rmv_existing()
1625 except SCons.Errors.StopError, drive:
1626 desc = "No drive `%s' for target `%s'." % (drive, self)
1627 raise SCons.Errors.StopError, desc
1630 """Remove this file."""
1631 if self.fs.exists_or_islink(self.path):
1632 self.fs.unlink(self.path)
1636 def do_duplicate(self, src):
1639 Unlink(self, None, None)
1640 except SCons.Errors.BuildError:
1643 Link(self, src, None)
1644 except SCons.Errors.BuildError, e:
1645 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1646 raise SCons.Errors.StopError, desc
1648 # The Link() action may or may not have actually
1649 # created the file, depending on whether the -n
1650 # option was used or not. Delete the _exists and
1651 # _rexists attributes so they can be reevaluated.
1656 # Duplicate from source path if we are set up to do this.
1657 if self.duplicate and not self.is_derived() and not self.linked:
1660 return Base.exists(self)
1662 if src.abspath != self.abspath and src.exists():
1663 self.do_duplicate(src)
1664 return Base.exists(self)
1666 def new_binfo(self):
1669 def del_cinfo(self):
1672 except AttributeError:
1675 del self.binfo.timestamp
1676 except AttributeError:
1679 def calc_csig(self, calc=None):
1681 Generate a node's content signature, the digested signature
1685 cache - alternate node to use for the signature cache
1686 returns - the content signature
1689 calc = self.calculator()
1692 return self.binfo.csig
1693 except AttributeError:
1696 if calc.max_drift >= 0:
1697 old = self.get_stored_info()
1702 mtime = self.get_timestamp()
1705 raise SCons.Errors.UserError, "no such %s" % self
1708 if (old.timestamp and old.csig and old.timestamp == mtime):
1709 # use the signature stored in the .sconsign file
1712 csig = calc.module.signature(self)
1713 except AttributeError:
1714 csig = calc.module.signature(self)
1716 if calc.max_drift >= 0 and (time.time() - mtime) > calc.max_drift:
1719 except AttributeError:
1720 binfo = self.binfo = self.new_binfo()
1722 binfo.timestamp = mtime
1723 self.store_info(binfo)
1727 def current(self, calc=None):
1728 self.binfo = self.gen_binfo(calc)
1732 if self.always_build:
1734 if not self.exists():
1735 # The file doesn't exist locally...
1738 # ...but there is one in a Repository...
1739 old = r.get_stored_info()
1740 if old == self.binfo:
1741 # ...and it's even up-to-date...
1743 # ...and they'd like a local copy.
1744 LocalCopy(self, r, None)
1745 self.store_info(self.binfo)
1749 old = self.get_stored_info()
1750 return (old == self.binfo)
1755 if not self.exists():
1756 n = self.fs.Rsearch(self.path, clazz=File,
1763 return str(self.rfile())
1765 def cachepath(self):
1766 if not self.fs.CachePath:
1768 if self.binfo.bsig is None:
1769 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1770 # Add the path to the cache signature, because multiple
1771 # targets built by the same action will all have the same
1772 # build signature, and we have to differentiate them somehow.
1773 cache_sig = SCons.Sig.MD5.collect([self.binfo.bsig, self.path])
1774 subdir = string.upper(cache_sig[0])
1775 dir = os.path.join(self.fs.CachePath, subdir)
1776 return dir, os.path.join(dir, cache_sig)
1778 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1779 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1781 def must_be_a_Dir(self):
1782 """Called to make sure a Node is a Dir. Since we're already a
1783 File, this is a TypeError..."""
1784 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1788 def find_file(filename, paths, node_factory=default_fs.File, verbose=None):
1790 find_file(str, [Dir()]) -> [nodes]
1792 filename - a filename to find
1793 paths - a list of directory path *nodes* to search in. Can be
1794 represented as a list, a tuple, or a callable that is
1795 called with no arguments and returns the list or tuple.
1797 returns - the node created from the found file.
1799 Find a node corresponding to either a derived file or a file
1800 that exists already.
1802 Only the first file found is returned, and none is returned
1803 if no file is found.
1806 if not SCons.Util.is_String(verbose):
1807 verbose = "find_file"
1808 if not callable(verbose):
1809 verbose = ' %s: ' % verbose
1810 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
1812 verbose = lambda x: x
1820 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
1822 node = node_factory(filename, dir)
1823 # Return true if the node exists or is a derived node.
1824 if node.is_derived() or \
1825 node.is_pseudo_derived() or \
1826 (isinstance(node, SCons.Node.FS.Base) and node.exists()):
1828 verbose("... FOUND '%s' in '%s'\n" % (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)