5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
8 This holds a "default_fs" variable that should be initialized with an FS
9 that can be used by scripts or modules looking for the canonical default.
16 # Permission is hereby granted, free of charge, to any person obtaining
17 # a copy of this software and associated documentation files (the
18 # "Software"), to deal in the Software without restriction, including
19 # without limitation the rights to use, copy, modify, merge, publish,
20 # distribute, sublicense, and/or sell copies of the Software, and to
21 # permit persons to whom the Software is furnished to do so, subject to
22 # the following conditions:
24 # The above copyright notice and this permission notice shall be included
25 # in all copies or substantial portions of the Software.
27 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
28 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
29 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
48 from SCons.Debug import logInstanceCreation
56 # We stringify these file system Nodes a lot. Turning a file system Node
57 # into a string is non-trivial, because the final string representation
58 # can depend on a lot of factors: whether it's a derived target or not,
59 # whether it's linked to a repository or source directory, and whether
60 # there's duplication going on. The normal technique for optimizing
61 # calculations like this is to memoize (cache) the string value, so you
62 # only have to do the calculation once.
64 # A number of the above factors, however, can be set after we've already
65 # been asked to return a string for a Node, because a Repository() or
66 # BuildDir() call or the like may not occur until later in SConscript
67 # files. So this variable controls whether we bother trying to save
68 # string values for Nodes. The wrapper interface can set this whenever
69 # they're done mucking with Repository and BuildDir and the other stuff,
70 # to let this module know it can start returning saved string values
75 def save_strings(val):
80 # SCons.Action objects for interacting with the outside world.
82 # The Node.FS methods in this module should use these actions to
83 # create and/or remove files and directories; they should *not* use
84 # os.{link,symlink,unlink,mkdir}(), etc., directly.
86 # Using these SCons.Action objects ensures that descriptions of these
87 # external activities are properly displayed, that the displays are
88 # suppressed when the -s (silent) option is used, and (most importantly)
89 # the actions are disabled when the the -n option is used, in which case
90 # there should be *no* changes to the external file system(s)...
93 def _copy_func(src, dest):
94 shutil.copy2(src, dest)
96 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
98 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
99 'hard-copy', 'soft-copy', 'copy']
101 Link_Funcs = [] # contains the callables of the specified duplication style
103 def set_duplicate(duplicate):
104 # Fill in the Link_Funcs list according to the argument
105 # (discarding those not available on the platform).
107 # Set up the dictionary that maps the argument names to the
108 # underlying implementations. We do this inside this function,
109 # not in the top-level module code, so that we can remap os.link
110 # and os.symlink for testing purposes.
112 _hardlink_func = os.link
113 except AttributeError:
114 _hardlink_func = None
117 _softlink_func = os.symlink
118 except AttributeError:
119 _softlink_func = None
122 'hard' : _hardlink_func,
123 'soft' : _softlink_func,
127 if not duplicate in Valid_Duplicates:
128 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
129 "should be in Valid_Duplicates")
132 for func in string.split(duplicate,'-'):
134 Link_Funcs.append(link_dict[func])
136 def LinkFunc(target, source, env):
137 # Relative paths cause problems with symbolic links, so
138 # we use absolute paths, which may be a problem for people
139 # who want to move their soft-linked src-trees around. Those
140 # people should use the 'hard-copy' mode, softlinks cannot be
141 # used for that; at least I have no idea how ...
142 src = source[0].abspath
143 dest = target[0].abspath
144 dir, file = os.path.split(dest)
145 if dir and not target[0].fs.isdir(dir):
148 # Set a default order of link functions.
149 set_duplicate('hard-soft-copy')
150 # Now link the files with the previously specified order.
151 for func in Link_Funcs:
156 if func == Link_Funcs[-1]:
157 # exception of the last link method (copy) are fatal
163 Link = SCons.Action.Action(LinkFunc, None)
164 def LocalString(target, source, env):
165 return 'Local copy of %s from %s' % (target[0], source[0])
167 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
169 def UnlinkFunc(target, source, env):
171 t.fs.unlink(t.abspath)
174 Unlink = SCons.Action.Action(UnlinkFunc, None)
176 def MkdirFunc(target, source, env):
179 if not t.fs.exists(p):
183 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
187 def get_MkdirBuilder():
189 if MkdirBuilder is None:
191 # "env" will get filled in by Executor.get_build_env()
192 # calling SCons.Defaults.DefaultEnvironment() when necessary.
193 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
197 name = "MkdirBuilder")
200 def CacheRetrieveFunc(target, source, env):
203 cachedir, cachefile = t.cachepath()
204 if fs.exists(cachefile):
205 if SCons.Action.execute_actions:
206 fs.copy2(cachefile, t.path)
207 st = fs.stat(cachefile)
208 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
212 def CacheRetrieveString(target, source, env):
214 cachedir, cachefile = t.cachepath()
215 if t.fs.exists(cachefile):
216 return "Retrieved `%s' from cache" % t.path
219 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
221 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
223 def CachePushFunc(target, source, env):
226 cachedir, cachefile = t.cachepath()
227 if fs.exists(cachefile):
228 # Don't bother copying it if it's already there.
231 if not fs.isdir(cachedir):
232 fs.makedirs(cachedir)
234 tempfile = cachefile+'.tmp'
236 fs.copy2(t.path, tempfile)
237 fs.rename(tempfile, cachefile)
239 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
241 # It's possible someone else tried writing the file at the same
242 # time we did. Print a warning but don't stop the build, since
243 # it doesn't affect the correctness of the build.
244 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
245 "Unable to copy %s to cache. Cache file is %s"
246 % (str(target), cachefile))
249 CachePush = SCons.Action.Action(CachePushFunc, None)
256 DefaultSCCSBuilder = None
257 DefaultRCSBuilder = None
259 def get_DefaultSCCSBuilder():
260 global DefaultSCCSBuilder
261 if DefaultSCCSBuilder is None:
263 # "env" will get filled in by Executor.get_build_env()
264 # calling SCons.Defaults.DefaultEnvironment() when necessary.
265 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
266 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
268 name = "DefaultSCCSBuilder")
269 return DefaultSCCSBuilder
271 def get_DefaultRCSBuilder():
272 global DefaultRCSBuilder
273 if DefaultRCSBuilder is None:
275 # "env" will get filled in by Executor.get_build_env()
276 # calling SCons.Defaults.DefaultEnvironment() when necessary.
277 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
278 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
280 name = "DefaultRCSBuilder")
281 return DefaultRCSBuilder
283 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
284 _is_cygwin = sys.platform == "cygwin"
285 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
290 return string.upper(x)
292 class EntryProxy(SCons.Util.Proxy):
293 def __get_abspath(self):
295 return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
296 entry.name + "_abspath")
298 def __get_filebase(self):
299 name = self.get().name
300 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
303 def __get_suffix(self):
304 name = self.get().name
305 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
308 def __get_file(self):
309 name = self.get().name
310 return SCons.Util.SpecialAttrWrapper(name, name + "_file")
312 def __get_base_path(self):
313 """Return the file's directory and file name, with the
316 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
317 entry.name + "_base")
319 def __get_posix_path(self):
320 """Return the path with / as the path separator,
321 regardless of platform."""
326 r = string.replace(entry.get_path(), os.sep, '/')
327 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_posix")
329 def __get_win32_path(self):
330 """Return the path with \ as the path separator,
331 regardless of platform."""
336 r = string.replace(entry.get_path(), os.sep, '\\')
337 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_win32")
339 def __get_srcnode(self):
340 return EntryProxy(self.get().srcnode())
342 def __get_srcdir(self):
343 """Returns the directory containing the source node linked to this
344 node via BuildDir(), or the directory of this node if not linked."""
345 return EntryProxy(self.get().srcnode().dir)
347 def __get_rsrcnode(self):
348 return EntryProxy(self.get().srcnode().rfile())
350 def __get_rsrcdir(self):
351 """Returns the directory containing the source node linked to this
352 node via BuildDir(), or the directory of this node if not linked."""
353 return EntryProxy(self.get().srcnode().rfile().dir)
356 return EntryProxy(self.get().dir)
358 dictSpecialAttrs = { "base" : __get_base_path,
359 "posix" : __get_posix_path,
360 "win32" : __get_win32_path,
361 "srcpath" : __get_srcnode,
362 "srcdir" : __get_srcdir,
364 "abspath" : __get_abspath,
365 "filebase" : __get_filebase,
366 "suffix" : __get_suffix,
368 "rsrcpath" : __get_rsrcnode,
369 "rsrcdir" : __get_rsrcdir,
372 def __getattr__(self, name):
373 # This is how we implement the "special" attributes
374 # such as base, posix, srcdir, etc.
376 return self.dictSpecialAttrs[name](self)
379 attr = SCons.Util.Proxy.__getattr__(self, name)
380 except AttributeError:
382 classname = string.split(str(entry.__class__), '.')[-1]
383 if classname[-2:] == "'>":
384 # new-style classes report their name as:
385 # "<class 'something'>"
386 # instead of the classic classes:
388 classname = classname[:-2]
389 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
392 class Base(SCons.Node.Node):
393 """A generic class for file system entries. This class is for
394 when we don't know yet whether the entry being looked up is a file
395 or a directory. Instances of this class can morph into either
396 Dir or File objects by a later, more precise lookup.
398 Note: this class does not define __cmp__ and __hash__ for
399 efficiency reasons. SCons does a lot of comparing of
400 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
401 as fast as possible, which means we want to use Python's built-in
402 object identity comparisons.
405 def __init__(self, name, directory, fs):
406 """Initialize a generic Node.FS.Base object.
408 Call the superclass initialization, take care of setting up
409 our relative and absolute paths, identify our parent
410 directory, and indicate that this node should use
412 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
413 SCons.Node.Node.__init__(self)
418 assert directory, "A directory must be provided"
420 self.abspath = directory.entry_abspath(name)
421 if directory.path == '.':
424 self.path = directory.entry_path(name)
425 if directory.tpath == '.':
428 self.tpath = directory.entry_tpath(name)
429 self.path_elements = directory.path_elements + [self]
432 self.cwd = None # will hold the SConscript directory for target nodes
433 self.duplicate = directory.duplicate
436 """Completely clear a Node.FS.Base object of all its cached
437 state (so that it can be re-evaluated by interfaces that do
438 continuous integration builds).
441 SCons.Node.Node.clear(self)
446 def get_suffix(self):
448 return SCons.Util.splitext(self.name)[1]
454 """A Node.FS.Base object's string representation is its path
458 return self._save_str()
459 return self._get_str()
463 return self._get_str()
466 if self.duplicate or self.is_derived():
467 return self.get_path()
468 return self.srcnode().get_path()
474 return self.fs.exists(self.abspath)
478 return self.rfile().exists()
480 def is_under(self, dir):
484 return self.dir.is_under(dir)
490 """If this node is in a build path, return the node
491 corresponding to its source file. Otherwise, return
498 srcnode = self.fs.Entry(name, dir.srcdir,
499 klass=self.__class__)
501 name = dir.name + os.sep + name
505 def get_path(self, dir=None):
506 """Return path relative to the current working directory of the
507 Node.FS.Base object that owns us."""
509 dir = self.fs.getcwd()
512 path_elems = self.path_elements
513 try: i = path_elems.index(dir)
514 except ValueError: pass
515 else: path_elems = path_elems[i+1:]
516 path_elems = map(lambda n: n.name, path_elems)
517 return string.join(path_elems, os.sep)
519 def set_src_builder(self, builder):
520 """Set the source code builder for this node."""
521 self.sbuilder = builder
522 if not self.has_builder():
523 self.builder_set(builder)
525 def src_builder(self):
526 """Fetch the source code builder for this node.
528 If there isn't one, we cache the source code builder specified
529 for the directory (which in turn will cache the value from its
530 parent directory, and so on up to the file system root).
534 except AttributeError:
535 scb = self.dir.src_builder()
539 def get_abspath(self):
540 """Get the absolute path of the file."""
543 def for_signature(self):
544 # Return just our name. Even an absolute path would not work,
545 # because that can change thanks to symlinks or remapped network
549 def get_subst_proxy(self):
552 except AttributeError:
553 ret = EntryProxy(self)
558 """This is the class for generic Node.FS entries--that is, things
559 that could be a File or a Dir, but we're just not sure yet.
560 Consequently, the methods in this class really exist just to
561 transform their associated object into the right class when the
562 time comes, and then call the same-named method in the transformed
565 def disambiguate(self):
566 if self.fs.isdir(self.abspath):
570 self.__class__ = File
576 """We're a generic Entry, but the caller is actually looking for
577 a File at this point, so morph into one."""
578 self.__class__ = File
581 return File.rfile(self)
583 def get_found_includes(self, env, scanner, path):
584 """If we're looking for included files, it's because this Entry
585 is really supposed to be a File itself."""
586 return self.disambiguate().get_found_includes(env, scanner, path)
588 def scanner_key(self):
589 return self.get_suffix()
591 def get_contents(self):
592 """Fetch the contents of the entry.
594 Since this should return the real contents from the file
595 system, we check to see into what sort of subclass we should
597 if self.fs.isfile(self.abspath):
598 self.__class__ = File
600 return self.get_contents()
601 if self.fs.isdir(self.abspath):
604 return self.get_contents()
605 if self.fs.islink(self.abspath):
606 return '' # avoid errors for dangling symlinks
609 def rel_path(self, other):
610 return self.disambiguate().rel_path(other)
613 """Return if the Entry exists. Check the file system to see
614 what we should turn into first. Assume a file if there's no
616 return self.disambiguate().exists()
618 def calc_signature(self, calc=None):
619 """Return the Entry's calculated signature. Check the file
620 system to see what we should turn into first. Assume a file if
621 there's no directory."""
622 return self.disambiguate().calc_signature(calc)
624 def must_be_a_Dir(self):
625 """Called to make sure a Node is a Dir. Since we're an
626 Entry, we can morph into one."""
631 # This is for later so we can differentiate between Entry the class and Entry
632 # the method of the FS class.
638 __metaclass__ = SCons.Memoize.Memoized_Metaclass
640 # This class implements an abstraction layer for operations involving
641 # a local file system. Essentially, this wraps any function in
642 # the os, os.path or shutil modules that we use to actually go do
643 # anything with or to the local file system.
645 # Note that there's a very good chance we'll refactor this part of
646 # the architecture in some way as we really implement the interface(s)
647 # for remote file system Nodes. For example, the right architecture
648 # might be to have this be a subclass instead of a base class.
649 # Nevertheless, we're using this as a first step in that direction.
651 # We're not using chdir() yet because the calling subclass method
652 # needs to use os.chdir() directly to avoid recursion. Will we
653 # really need this one?
654 #def chdir(self, path):
655 # return os.chdir(path)
656 def chmod(self, path, mode):
657 return os.chmod(path, mode)
658 def copy2(self, src, dst):
659 return shutil.copy2(src, dst)
660 def exists(self, path):
661 return os.path.exists(path)
662 def getmtime(self, path):
663 return os.path.getmtime(path)
664 def isdir(self, path):
665 return os.path.isdir(path)
666 def isfile(self, path):
667 return os.path.isfile(path)
668 def link(self, src, dst):
669 return os.link(src, dst)
670 def listdir(self, path):
671 return os.listdir(path)
672 def makedirs(self, path):
673 return os.makedirs(path)
674 def mkdir(self, path):
675 return os.mkdir(path)
676 def rename(self, old, new):
677 return os.rename(old, new)
678 def stat(self, path):
680 def symlink(self, src, dst):
681 return os.symlink(src, dst)
682 def open(self, path):
684 def unlink(self, path):
685 return os.unlink(path)
687 if hasattr(os, 'symlink'):
688 def islink(self, path):
689 return os.path.islink(path)
690 def exists_or_islink(self, path):
691 return os.path.exists(path) or os.path.islink(path)
693 def islink(self, path):
694 return 0 # no symlinks
695 exists_or_islink = exists
697 if not SCons.Memoize.has_metaclass:
699 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
700 def __init__(self, *args, **kw):
701 apply(_FSBase.__init__, (self,)+args, kw)
702 SCons.Memoize.Memoizer.__init__(self)
706 # # Skeleton for the obvious methods we might need from the
707 # # abstraction layer for a remote filesystem.
708 # def upload(self, local_src, remote_dst):
710 # def download(self, remote_src, local_dst):
716 def __init__(self, path = None):
717 """Initialize the Node.FS subsystem.
719 The supplied path is the top of the source tree, where we
720 expect to find the top-level build file. If no path is
721 supplied, the current directory is the default.
723 The path argument must be a valid absolute path.
725 if __debug__: logInstanceCreation(self, 'Node.FS')
727 self.SConstruct_dir = None
728 self.CachePath = None
729 self.cache_force = None
730 self.cache_show = None
733 self.pathTop = os.getcwd()
737 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
742 def clear_cache(self):
746 def set_SConstruct_dir(self, dir):
747 self.SConstruct_dir = dir
752 def __checkClass(self, node, klass):
753 if isinstance(node, klass) or klass == Entry:
755 if node.__class__ == Entry:
756 node.__class__ = klass
759 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
760 (node.__class__.__name__, node.path, klass.__name__)
762 def _doLookup(self, fsclass, name, directory = None, create = 1):
763 """This method differs from the File and Dir factory methods in
764 one important way: the meaning of the directory parameter.
765 In this method, if directory is None or not supplied, the supplied
766 name is expected to be an absolute path. If you try to look up a
767 relative path with directory=None, then an AssertionError will be
772 # This is a stupid hack to compensate for the fact that
773 # the POSIX and Win32 versions of os.path.normpath() behave
774 # differently in older versions of Python. In particular,
776 # os.path.normpath('./') == '.'
778 # os.path.normpath('./') == ''
779 # os.path.normpath('.\\') == ''
781 # This is a definite bug in the Python library, but we have
784 path_orig = string.split(name, os.sep)
785 path_norm = string.split(_my_normcase(name), os.sep)
787 first_orig = path_orig.pop(0) # strip first element
788 first_norm = path_norm.pop(0) # strip first element
790 drive, path_first = os.path.splitdrive(first_orig)
792 path_orig = [ path_first, ] + path_orig
793 path_norm = [ _my_normcase(path_first), ] + path_norm
797 directory = self.Root[drive]
800 raise SCons.Errors.UserError
801 directory = RootDir(drive, self)
802 self.Root[drive] = directory
807 last_orig = path_orig.pop() # strip last element
808 last_norm = path_norm.pop() # strip last element
810 # Lookup the directory
811 for orig, norm in map(None, path_orig, path_norm):
813 directory = directory.entries[norm]
816 raise SCons.Errors.UserError
818 # look at the actual filesystem and make sure there isn't
819 # a file already there
820 path = directory.entry_path(orig)
821 if self.isfile(path):
823 "File %s found where directory expected." % path
825 d = Dir(orig, directory, self)
826 directory.entries[norm] = d
827 directory.add_wkid(d)
829 except AttributeError:
830 # We tried to look up the entry in either an Entry or
831 # a File. Give whatever it is a chance to do what's
832 # appropriate: morph into a Dir or raise an exception.
833 directory.must_be_a_Dir()
834 directory = directory.entries[norm]
836 directory.must_be_a_Dir()
839 e = directory.entries[last_norm]
842 raise SCons.Errors.UserError
844 # make sure we don't create File nodes when there is actually
845 # a directory at that path on the disk, and vice versa
846 path = directory.entry_path(last_orig)
850 "Directory %s found where file expected." % path
852 if self.isfile(path):
854 "File %s found where directory expected." % path
856 result = fsclass(last_orig, directory, self)
857 directory.entries[last_norm] = result
858 directory.add_wkid(result)
860 result = self.__checkClass(e, fsclass)
863 def _transformPath(self, name, directory):
864 """Take care of setting up the correct top-level directory,
865 usually in preparation for a call to doLookup().
867 If the path name is prepended with a '#', then it is unconditionally
868 interpreted as relative to the top-level directory of this FS.
870 If directory is None, and name is a relative path,
871 then the same applies.
873 if name and name[0] == '#':
876 if name and (name[0] == os.sep or name[0] == '/'):
877 # Correct such that '#/foo' is equivalent
880 name = os.path.join('.', os.path.normpath(name))
882 directory = self._cwd
883 return (os.path.normpath(name), directory)
885 def chdir(self, dir, change_os_dir=0):
886 """Change the current working directory for lookups.
887 If change_os_dir is true, we will also change the "real" cwd
895 os.chdir(dir.abspath)
900 def Entry(self, name, directory = None, create = 1, klass=None):
901 """Lookup or create a generic Entry node with the specified name.
902 If the name is a relative path (begins with ./, ../, or a file
903 name), then it is looked up relative to the supplied directory
904 node, or to the top level directory of the FS (supplied at
905 construction time) if no directory is supplied.
911 if isinstance(name, Base):
912 return self.__checkClass(name, klass)
914 if directory and not isinstance(directory, Dir):
915 directory = self.Dir(directory)
916 name, directory = self._transformPath(name, directory)
917 return self._doLookup(klass, name, directory, create)
919 def File(self, name, directory = None, create = 1):
920 """Lookup or create a File node with the specified name. If
921 the name is a relative path (begins with ./, ../, or a file name),
922 then it is looked up relative to the supplied directory node,
923 or to the top level directory of the FS (supplied at construction
924 time) if no directory is supplied.
926 This method will raise TypeError if a directory is found at the
930 return self.Entry(name, directory, create, File)
932 def Dir(self, name, directory = None, create = 1):
933 """Lookup or create a Dir node with the specified name. If
934 the name is a relative path (begins with ./, ../, or a file name),
935 then it is looked up relative to the supplied directory node,
936 or to the top level directory of the FS (supplied at construction
937 time) if no directory is supplied.
939 This method will raise TypeError if a normal file is found at the
943 return self.Entry(name, directory, create, Dir)
945 def BuildDir(self, build_dir, src_dir, duplicate=1):
946 """Link the supplied build directory to the source directory
947 for purposes of building files."""
949 if not isinstance(src_dir, SCons.Node.Node):
950 src_dir = self.Dir(src_dir)
951 if not isinstance(build_dir, SCons.Node.Node):
952 build_dir = self.Dir(build_dir)
953 if not src_dir.is_under(self.Top):
954 raise SCons.Errors.UserError, "Source directory must be under top of build tree."
955 if src_dir.is_under(build_dir):
956 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
958 if build_dir.srcdir == src_dir:
959 return # We already did this.
960 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
961 build_dir.link(src_dir, duplicate)
963 def Repository(self, *dirs):
964 """Specify Repository directories to search."""
966 if not isinstance(d, SCons.Node.Node):
968 self.Top.addRepository(d)
970 def Rfindalldirs(self, pathlist, cwd):
972 if SCons.Util.is_String(pathlist):
973 pathlist = string.split(pathlist, os.pathsep)
974 if not SCons.Util.is_List(pathlist):
975 pathlist = [pathlist]
977 for path in filter(None, pathlist):
978 if isinstance(path, SCons.Node.Node):
981 path, dir = self._transformPath(path, cwd)
983 result.extend(dir.get_all_rdirs())
986 def CacheDir(self, path):
987 self.CachePath = path
989 def build_dir_target_climb(self, orig, dir, tail):
990 """Create targets in corresponding build directories
992 Climb the directory tree, and look up path names
993 relative to any linked build directories we find.
998 fmt = "building associated BuildDir targets: %s"
1001 for bd in dir.build_dirs:
1002 if start_dir.is_under(bd):
1003 # If already in the build-dir location, don't reflect
1004 return [orig], fmt % str(orig)
1005 p = apply(os.path.join, [bd.path] + tail)
1006 targets.append(self.Entry(p))
1007 tail = [dir.name] + tail
1010 message = fmt % string.join(map(str, targets))
1011 return targets, message
1014 """A class for directories in a file system.
1017 def __init__(self, name, directory, fs):
1018 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1019 Base.__init__(self, name, directory, fs)
1023 """Turn a file system Node (either a freshly initialized directory
1024 object or a separate Entry object) into a proper directory object.
1026 Set up this directory's entries and hook it into the file
1027 system tree. Specify that directories (this Node) don't use
1028 signatures for calculating whether they're current.
1031 self.repositories = []
1035 self.entries['.'] = self
1036 self.entries['..'] = self.dir
1038 self.builder = get_MkdirBuilder()
1040 self._sconsign = None
1041 self.build_dirs = []
1043 def disambiguate(self):
1046 def __clearRepositoryCache(self, duplicate=None):
1047 """Called when we change the repository(ies) for a directory.
1048 This clears any cached information that is invalidated by changing
1051 for node in self.entries.values():
1052 if node != self.dir:
1053 if node != self and isinstance(node, Dir):
1054 node.__clearRepositoryCache(duplicate)
1059 except AttributeError:
1061 if duplicate != None:
1062 node.duplicate=duplicate
1064 def __resetDuplicate(self, node):
1066 node.duplicate = node.get_dir().duplicate
1068 def Entry(self, name):
1069 """Create an entry node named 'name' relative to this directory."""
1070 return self.fs.Entry(name, self)
1072 def Dir(self, name):
1073 """Create a directory node named 'name' relative to this directory."""
1074 return self.fs.Dir(name, self)
1076 def File(self, name):
1077 """Create a file node named 'name' relative to this directory."""
1078 return self.fs.File(name, self)
1080 def link(self, srcdir, duplicate):
1081 """Set this directory as the build directory for the
1082 supplied source directory."""
1083 self.srcdir = srcdir
1084 self.duplicate = duplicate
1085 self.__clearRepositoryCache(duplicate)
1086 srcdir.build_dirs.append(self)
1088 def getRepositories(self):
1089 """Returns a list of repositories for this directory.
1091 if self.srcdir and not self.duplicate:
1092 return self.srcdir.get_all_rdirs() + self.repositories
1093 return self.repositories
1095 def get_all_rdirs(self):
1101 for rep in dir.getRepositories():
1102 result.append(rep.Dir(fname))
1103 fname = dir.name + os.sep + fname
1107 def addRepository(self, dir):
1108 if dir != self and not dir in self.repositories:
1109 self.repositories.append(dir)
1111 self.__clearRepositoryCache()
1114 return self.entries['..']
1116 def rel_path(self, other):
1117 """Return a path to "other" relative to this directory.
1119 if isinstance(other, Dir):
1125 except AttributeError:
1128 return name and name[0] or '.'
1130 for x, y in map(None, self.path_elements, other.path_elements):
1134 path_elems = ['..']*(len(self.path_elements)-i) \
1135 + map(lambda n: n.name, other.path_elements[i:]) \
1138 return string.join(path_elems, os.sep)
1141 if not self.implicit is None:
1144 self.implicit_dict = {}
1145 self._children_reset()
1147 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1148 deps = filter(dont_scan, self.entries.keys())
1149 # keys() is going to give back the entries in an internal,
1150 # unsorted order. Sort 'em so the order is deterministic.
1152 entries = map(lambda n, e=self.entries: e[n], deps)
1154 self._add_child(self.implicit, self.implicit_dict, entries)
1156 def get_found_includes(self, env, scanner, path):
1157 """Return the included implicit dependencies in this file.
1158 Cache results so we only scan the file once per path
1159 regardless of how many times this information is requested.
1163 # Clear cached info for this Node. If we already visited this
1164 # directory on our walk down the tree (because we didn't know at
1165 # that point it was being used as the source for another Node)
1166 # then we may have calculated build signature before realizing
1167 # we had to scan the disk. Now that we have to, though, we need
1168 # to invalidate the old calculated signature so that any node
1169 # dependent on our directory structure gets one that includes
1170 # info about everything on disk.
1172 return scanner(self, env, path)
1174 def build(self, **kw):
1175 """A null "builder" for directories."""
1177 if not self.builder is MkdirBuilder:
1178 apply(SCons.Node.Node.build, [self,], kw)
1181 """Create this directory, silently and without worrying about
1182 whether the builder is the default or not."""
1188 listDirs.append(parent)
1191 raise SCons.Errors.StopError, parent.path
1194 for dirnode in listDirs:
1196 # Don't call dirnode.build(), call the base Node method
1197 # directly because we definitely *must* create this
1198 # directory. The dirnode.build() method will suppress
1199 # the build if it's the default builder.
1200 SCons.Node.Node.build(dirnode)
1201 dirnode.get_executor().nullify()
1202 # The build() action may or may not have actually
1203 # created the directory, depending on whether the -n
1204 # option was used or not. Delete the _exists and
1205 # _rexists attributes so they can be reevaluated.
1210 def multiple_side_effect_has_builder(self):
1212 return not self.builder is MkdirBuilder and self.has_builder()
1214 def alter_targets(self):
1215 """Return any corresponding targets in a build directory.
1217 return self.fs.build_dir_target_climb(self, self, [])
1219 def scanner_key(self):
1220 """A directory does not get scanned."""
1223 def get_contents(self):
1224 """Return aggregate contents of all our children."""
1225 contents = cStringIO.StringIO()
1226 for kid in self.children():
1227 contents.write(kid.get_contents())
1228 return contents.getvalue()
1233 def do_duplicate(self, src):
1236 def current(self, calc=None):
1237 """If all of our children were up-to-date, then this
1238 directory was up-to-date, too."""
1239 if not self.builder is MkdirBuilder and not self.exists():
1242 for kid in self.children():
1244 if s and (not state or s > state):
1247 if state == 0 or state == SCons.Node.up_to_date:
1254 if not self.exists():
1255 norm_name = _my_normcase(self.name)
1256 for dir in self.dir.get_all_rdirs():
1257 try: node = dir.entries[norm_name]
1258 except KeyError: node = dir.dir_on_disk(self.name)
1259 if node and node.exists() and \
1260 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1265 """Return the .sconsign file info for this directory,
1266 creating it first if necessary."""
1267 if not self._sconsign:
1268 import SCons.SConsign
1269 self._sconsign = SCons.SConsign.ForDirectory(self)
1270 return self._sconsign
1273 """Dir has a special need for srcnode()...if we
1274 have a srcdir attribute set, then that *is* our srcnode."""
1277 return Base.srcnode(self)
1279 def get_timestamp(self):
1280 """Return the latest timestamp from among our children"""
1282 for kid in self.children():
1283 if kid.get_timestamp() > stamp:
1284 stamp = kid.get_timestamp()
1287 def entry_abspath(self, name):
1288 return self.abspath + os.sep + name
1290 def entry_path(self, name):
1291 return self.path + os.sep + name
1293 def entry_tpath(self, name):
1294 return self.tpath + os.sep + name
1296 def must_be_a_Dir(self):
1297 """Called to make sure a Node is a Dir. Since we're already
1298 one, this is a no-op for us."""
1301 def entry_exists_on_disk(self, name):
1303 return self.fs.exists(self.entry_abspath(name))
1305 def rcs_on_disk(self, name):
1306 rcspath = 'RCS' + os.sep + name+',v'
1307 return self.entry_exists_on_disk(rcspath)
1309 def sccs_on_disk(self, name):
1310 sccspath = 'SCCS' + os.sep + 's.'+name
1311 return self.entry_exists_on_disk(sccspath)
1313 def srcdir_list(self):
1321 d = dir.srcdir.Dir(dirname)
1323 # Shouldn't source from something in the build path:
1324 # build_dir is probably under src_dir, in which case
1325 # we are reflecting.
1328 dirname = dir.name + os.sep + dirname
1333 def srcdir_duplicate(self, name):
1334 for dir in self.srcdir_list():
1335 if dir.entry_exists_on_disk(name):
1336 srcnode = dir.File(name)
1338 node = self.File(name)
1339 node.do_duplicate(srcnode)
1345 def srcdir_find_file(self, filename):
1348 if (isinstance(node, File) or isinstance(node, Entry)) and \
1349 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1353 norm_name = _my_normcase(filename)
1355 for rdir in self.get_all_rdirs():
1356 try: node = rdir.entries[norm_name]
1357 except KeyError: node = rdir.file_on_disk(filename)
1358 else: node = func(node)
1362 for srcdir in self.srcdir_list():
1363 for rdir in srcdir.get_all_rdirs():
1364 try: node = rdir.entries[norm_name]
1365 except KeyError: node = rdir.file_on_disk(filename)
1366 else: node = func(node)
1368 return File(filename, self, self.fs), srcdir
1372 def dir_on_disk(self, name):
1373 if self.entry_exists_on_disk(name):
1374 try: return self.Dir(name)
1375 except TypeError: pass
1378 def file_on_disk(self, name):
1379 if self.entry_exists_on_disk(name) or \
1380 self.sccs_on_disk(name) or \
1381 self.rcs_on_disk(name):
1382 try: return self.File(name)
1383 except TypeError: pass
1384 return self.srcdir_duplicate(name)
1387 """A class for the root directory of a file system.
1389 This is the same as a Dir class, except that the path separator
1390 ('/' or '\\') is actually part of the name, so we don't need to
1391 add a separator when creating the path names of entries within
1394 def __init__(self, name, fs):
1395 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1396 # We're going to be our own parent directory (".." entry and .dir
1397 # attribute) so we have to set up some values so Base.__init__()
1398 # won't gag won't it calls some of our methods.
1402 self.path_elements = []
1404 Base.__init__(self, name, self, fs)
1406 # Now set our paths to what we really want them to be: the
1407 # initial drive letter (the name) plus the directory separator.
1408 self.abspath = name + os.sep
1409 self.path = name + os.sep
1410 self.tpath = name + os.sep
1416 def entry_abspath(self, name):
1417 return self.abspath + name
1419 def entry_path(self, name):
1420 return self.path + name
1422 def entry_tpath(self, name):
1423 return self.tpath + name
1425 def is_under(self, dir):
1437 def src_builder(self):
1442 def __cmp__(self, other):
1444 return cmp(self.bsig, other.bsig)
1445 except AttributeError:
1449 """A class for files in a file system.
1451 def __init__(self, name, directory, fs):
1452 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1453 Base.__init__(self, name, directory, fs)
1456 def Entry(self, name):
1457 """Create an entry node named 'name' relative to
1458 the SConscript directory of this file."""
1459 return self.fs.Entry(name, self.cwd)
1461 def Dir(self, name):
1462 """Create a directory node named 'name' relative to
1463 the SConscript directory of this file."""
1464 return self.fs.Dir(name, self.cwd)
1466 def File(self, name):
1467 """Create a file node named 'name' relative to
1468 the SConscript directory of this file."""
1469 return self.fs.File(name, self.cwd)
1471 def RDirs(self, pathlist):
1472 """Search for a list of directories in the Repository list."""
1473 return self.fs.Rfindalldirs(pathlist, self.cwd)
1476 """Turn a file system node into a File object. __cache_reset__"""
1477 self.scanner_paths = {}
1478 if not hasattr(self, '_local'):
1481 def disambiguate(self):
1484 def scanner_key(self):
1485 return self.get_suffix()
1487 def get_contents(self):
1488 if not self.rexists():
1490 return open(self.rfile().abspath, "rb").read()
1492 def get_timestamp(self):
1494 return self.fs.getmtime(self.rfile().abspath)
1498 def store_info(self, obj):
1499 # Merge our build information into the already-stored entry.
1500 # This accomodates "chained builds" where a file that's a target
1501 # in one build (SConstruct file) is a source in a different build.
1502 # See test/chained-build.py for the use case.
1503 entry = self.get_stored_info()
1504 for key, val in obj.__dict__.items():
1505 entry.__dict__[key] = val
1506 self.dir.sconsign().set_entry(self.name, entry)
1508 def get_stored_info(self):
1511 stored = self.dir.sconsign().get_entry(self.name)
1512 except (KeyError, OSError):
1515 if isinstance(stored, BuildInfo):
1517 # The stored build information isn't a BuildInfo object.
1518 # This probably means it's an old SConsignEntry from SCons
1519 # 0.95 or before. The relevant attribute names are the same,
1520 # though, so just copy the attributes over to an object of
1523 for key, val in stored.__dict__.items():
1524 setattr(binfo, key, val)
1527 def get_stored_implicit(self):
1528 binfo = self.get_stored_info()
1529 try: implicit = binfo.bimplicit
1530 except AttributeError: return None
1531 else: return map(self.dir.Entry, implicit)
1533 def rel_path(self, other):
1534 return self.dir.rel_path(other)
1536 def get_found_includes(self, env, scanner, path):
1537 """Return the included implicit dependencies in this file.
1538 Cache results so we only scan the file once per path
1539 regardless of how many times this information is requested.
1543 return scanner(self, env, path)
1545 def _createDir(self):
1546 # ensure that the directories for this node are
1550 def retrieve_from_cache(self):
1551 """Try to retrieve the node's content from a cache
1553 This method is called from multiple threads in a parallel build,
1554 so only do thread safe stuff here. Do thread unsafe stuff in
1557 Note that there's a special trick here with the execute flag
1558 (one that's not normally done for other actions). Basically
1559 if the user requested a noexec (-n) build, then
1560 SCons.Action.execute_actions is set to 0 and when any action
1561 is called, it does its showing but then just returns zero
1562 instead of actually calling the action execution operation.
1563 The problem for caching is that if the file does NOT exist in
1564 cache then the CacheRetrieveString won't return anything to
1565 show for the task, but the Action.__call__ won't call
1566 CacheRetrieveFunc; instead it just returns zero, which makes
1567 the code below think that the file *was* successfully
1568 retrieved from the cache, therefore it doesn't do any
1569 subsequent building. However, the CacheRetrieveString didn't
1570 print anything because it didn't actually exist in the cache,
1571 and no more build actions will be performed, so the user just
1572 sees nothing. The fix is to tell Action.__call__ to always
1573 execute the CacheRetrieveFunc and then have the latter
1574 explicitly check SCons.Action.execute_actions itself.
1576 Returns true iff the node was successfully retrieved.
1578 b = self.is_derived()
1579 if not b and not self.has_src_builder():
1581 if b and self.fs.CachePath:
1582 if self.fs.cache_show:
1583 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1584 self.build(presub=0, execute=0)
1586 elif CacheRetrieve(self, [], None, execute=1) == 0:
1591 """Called just after this node is successfully built.
1593 # Push this file out to cache before the superclass Node.built()
1594 # method has a chance to clear the build signature, which it
1595 # will do if this file has a source scanner.
1596 if self.fs.CachePath and self.fs.exists(self.path):
1597 CachePush(self, [], None)
1598 self.fs.clear_cache()
1599 SCons.Node.Node.built(self)
1602 if self.fs.CachePath and self.fs.cache_force and self.fs.exists(self.path):
1603 CachePush(self, None, None)
1605 def has_src_builder(self):
1606 """Return whether this Node has a source builder or not.
1608 If this Node doesn't have an explicit source code builder, this
1609 is where we figure out, on the fly, if there's a transparent
1610 source code builder for it.
1612 Note that if we found a source builder, we also set the
1613 self.builder attribute, so that all of the methods that actually
1614 *build* this file don't have to do anything different.
1618 except AttributeError:
1622 scb = self.dir.src_builder()
1624 if self.dir.sccs_on_disk(self.name):
1625 scb = get_DefaultSCCSBuilder()
1626 elif self.dir.rcs_on_disk(self.name):
1627 scb = get_DefaultRCSBuilder()
1631 self.builder_set(scb)
1633 return not scb is None
1635 def alter_targets(self):
1636 """Return any corresponding targets in a build directory.
1638 if self.is_derived():
1640 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1642 def is_pseudo_derived(self):
1644 return self.has_src_builder()
1646 def _rmv_existing(self):
1648 Unlink(self, [], None)
1651 """Prepare for this file to be created."""
1652 SCons.Node.Node.prepare(self)
1654 if self.get_state() != SCons.Node.up_to_date:
1656 if self.is_derived() and not self.precious:
1657 self._rmv_existing()
1661 except SCons.Errors.StopError, drive:
1662 desc = "No drive `%s' for target `%s'." % (drive, self)
1663 raise SCons.Errors.StopError, desc
1666 """Remove this file."""
1667 if self.fs.exists_or_islink(self.path):
1668 self.fs.unlink(self.path)
1672 def do_duplicate(self, src):
1675 Unlink(self, None, None)
1676 except SCons.Errors.BuildError:
1679 Link(self, src, None)
1680 except SCons.Errors.BuildError, e:
1681 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1682 raise SCons.Errors.StopError, desc
1684 # The Link() action may or may not have actually
1685 # created the file, depending on whether the -n
1686 # option was used or not. Delete the _exists and
1687 # _rexists attributes so they can be reevaluated.
1692 # Duplicate from source path if we are set up to do this.
1693 if self.duplicate and not self.is_derived() and not self.linked:
1696 return Base.exists(self)
1698 if src.abspath != self.abspath and src.exists():
1699 self.do_duplicate(src)
1700 return Base.exists(self)
1702 def new_binfo(self):
1705 def del_cinfo(self):
1708 except AttributeError:
1711 del self.binfo.timestamp
1712 except AttributeError:
1715 def calc_csig(self, calc=None):
1717 Generate a node's content signature, the digested signature
1721 cache - alternate node to use for the signature cache
1722 returns - the content signature
1725 calc = self.calculator()
1728 return self.binfo.csig
1729 except AttributeError:
1732 if calc.max_drift >= 0:
1733 old = self.get_stored_info()
1738 mtime = self.get_timestamp()
1741 raise SCons.Errors.UserError, "no such %s" % self
1744 if (old.timestamp and old.csig and old.timestamp == mtime):
1745 # use the signature stored in the .sconsign file
1748 csig = calc.module.signature(self)
1749 except AttributeError:
1750 csig = calc.module.signature(self)
1752 if calc.max_drift >= 0 and (time.time() - mtime) > calc.max_drift:
1755 except AttributeError:
1756 binfo = self.binfo = self.new_binfo()
1758 binfo.timestamp = mtime
1759 self.store_info(binfo)
1763 def current(self, calc=None):
1764 self.binfo = self.gen_binfo(calc)
1768 if self.always_build:
1770 if not self.exists():
1771 # The file doesn't exist locally...
1774 # ...but there is one in a Repository...
1775 old = r.get_stored_info()
1776 if old == self.binfo:
1777 # ...and it's even up-to-date...
1779 # ...and they'd like a local copy.
1780 LocalCopy(self, r, None)
1781 self.store_info(self.binfo)
1785 old = self.get_stored_info()
1786 return (old == self.binfo)
1790 if not self.exists():
1791 norm_name = _my_normcase(self.name)
1792 for dir in self.dir.get_all_rdirs():
1793 try: node = dir.entries[norm_name]
1794 except KeyError: node = dir.file_on_disk(self.name)
1795 if node and node.exists() and \
1796 (isinstance(node, File) or isinstance(node, Entry) \
1797 or not node.is_derived()):
1802 return str(self.rfile())
1804 def cachepath(self):
1805 if not self.fs.CachePath:
1807 if self.binfo.bsig is None:
1808 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1809 # Add the path to the cache signature, because multiple
1810 # targets built by the same action will all have the same
1811 # build signature, and we have to differentiate them somehow.
1812 cache_sig = SCons.Sig.MD5.collect([self.binfo.bsig, self.path])
1813 subdir = string.upper(cache_sig[0])
1814 dir = os.path.join(self.fs.CachePath, subdir)
1815 return dir, os.path.join(dir, cache_sig)
1817 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1818 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1820 def must_be_a_Dir(self):
1821 """Called to make sure a Node is a Dir. Since we're already a
1822 File, this is a TypeError..."""
1823 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1827 def find_file(filename, paths, verbose=None):
1829 find_file(str, [Dir()]) -> [nodes]
1831 filename - a filename to find
1832 paths - a list of directory path *nodes* to search in. Can be
1833 represented as a list, a tuple, or a callable that is
1834 called with no arguments and returns the list or tuple.
1836 returns - the node created from the found file.
1838 Find a node corresponding to either a derived file or a file
1839 that exists already.
1841 Only the first file found is returned, and none is returned
1842 if no file is found.
1846 if not SCons.Util.is_String(verbose):
1847 verbose = "find_file"
1848 if not callable(verbose):
1849 verbose = ' %s: ' % verbose
1850 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
1852 verbose = lambda x: x
1857 # Give Entries a chance to morph into Dirs.
1858 paths = map(lambda p: p.must_be_a_Dir(), paths)
1860 filedir, filename = os.path.split(filename)
1862 def filedir_lookup(p, fd=filedir):
1866 # We tried to look up a Dir, but it seems there's already
1867 # a File (or something else) there. No big.
1869 paths = filter(None, map(filedir_lookup, paths))
1872 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
1873 node, d = dir.srcdir_find_file(filename)
1875 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
1879 def find_files(filenames, paths):
1881 find_files([str], [Dir()]) -> [nodes]
1883 filenames - a list of filenames to find
1884 paths - a list of directory path *nodes* to search in
1886 returns - the nodes created from the found files.
1888 Finds nodes corresponding to either derived files or files
1891 Only the first file found is returned for each filename,
1892 and any files that aren't found are ignored.
1894 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
1895 return filter(None, nodes)