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__"
52 # SCons.Action objects for interacting with the outside world.
54 # The Node.FS methods in this module should use these actions to
55 # create and/or remove files and directories; they should *not* use
56 # os.{link,symlink,unlink,mkdir}(), etc., directly.
58 # Using these SCons.Action objects ensures that descriptions of these
59 # external activities are properly displayed, that the displays are
60 # suppressed when the -s (silent) option is used, and (most importantly)
61 # the actions are disabled when the the -n option is used, in which case
62 # there should be *no* changes to the external file system(s)...
65 if hasattr(os, 'symlink'):
67 return os.path.exists(p) or os.path.islink(p)
69 _existsp = os.path.exists
71 def LinkFunc(target, source, env):
74 dir, file = os.path.split(dest)
75 if dir and not os.path.isdir(dir):
77 # Now actually link the files. First try to make a hard link. If that
78 # fails, try a symlink. If that fails then just copy it.
81 except (AttributeError, OSError):
84 except (AttributeError, OSError):
85 shutil.copy2(src, dest)
87 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
90 Link = SCons.Action.Action(LinkFunc, None)
92 def LocalString(target, source, env):
93 return 'Local copy of %s from %s' % (target[0], source[0])
95 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
97 def UnlinkFunc(target, source, env):
98 os.unlink(target[0].path)
101 Unlink = SCons.Action.Action(UnlinkFunc, None)
103 def MkdirFunc(target, source, env):
104 os.mkdir(target[0].path)
107 Mkdir = SCons.Action.Action(MkdirFunc, None)
109 def CacheRetrieveFunc(target, source, env):
111 cachedir, cachefile = t.cachepath()
112 if os.path.exists(cachefile):
113 shutil.copy2(cachefile, t.path)
114 st = os.stat(cachefile)
115 os.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
119 def CacheRetrieveString(target, source, env):
121 cachedir, cachefile = t.cachepath()
122 if os.path.exists(cachefile):
123 return "Retrieved `%s' from cache" % t.path
126 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
128 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
130 def CachePushFunc(target, source, env):
132 cachedir, cachefile = t.cachepath()
133 if os.path.exists(cachefile):
134 # Don't bother copying it if it's already there.
137 if not os.path.isdir(cachedir):
140 tempfile = cachefile+'.tmp'
142 shutil.copy2(t.path, tempfile)
143 os.rename(tempfile, cachefile)
145 os.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
147 # It's possible someone else tried writing the file at the same
148 # time we did. Print a warning but don't stop the build, since
149 # it doesn't affect the correctness of the build.
150 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
151 "Unable to copy %s to cache. Cache file is %s"
152 % (str(target), cachefile))
155 CachePush = SCons.Action.Action(CachePushFunc, None)
162 DefaultSCCSBuilder = None
163 DefaultRCSBuilder = None
165 def get_DefaultSCCSBuilder():
166 global DefaultSCCSBuilder
167 if DefaultSCCSBuilder is None:
169 import SCons.Defaults
170 DefaultSCCSBuilder = SCons.Builder.Builder(action = '$SCCSCOM',
171 env = SCons.Defaults._default_env)
172 return DefaultSCCSBuilder
174 def get_DefaultRCSBuilder():
175 global DefaultRCSBuilder
176 if DefaultRCSBuilder is None:
178 import SCons.Defaults
179 DefaultRCSBuilder = SCons.Builder.Builder(action = '$RCS_COCOM',
180 env = SCons.Defaults._default_env)
181 return DefaultRCSBuilder
186 An instance of this class is used as the parent of the root of a
187 filesystem (POSIX) or drive (Win32). This isn't actually a node,
188 but it looks enough like one so that we don't have to have
189 special purpose code everywhere to deal with dir being None.
190 This class is an instance of the Null object pattern.
202 def is_under(self, dir):
208 def getRepositories(self):
214 def recurse_get_path(self, dir, path_elems):
217 def src_builder(self):
220 if os.path.normcase("TeSt") == os.path.normpath("TeSt"):
225 return string.upper(x)
227 class EntryProxy(SCons.Util.Proxy):
228 def __get_abspath(self):
230 return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
231 entry.name + "_abspath")
233 def __get_filebase(self):
234 name = self.get().name
235 return SCons.Util.SpecialAttrWrapper(os.path.splitext(name)[0],
238 def __get_suffix(self):
239 entry.name = self.get().name
240 return SCons.Util.SpecialAttrWrapper(os.path.splitext(name)[1],
243 def __get_file(self):
244 entry.name = self.get().name
245 return SCons.Util.SpecialAttrWrapper(name, name + "_file")
247 def __get_base_path(self):
248 """Return the file's directory and file name, with the
251 return SCons.Util.SpecialAttrWrapper(os.path.splitext(entry.get_path())[0],
252 entry.name + "_base")
254 def __get_posix_path(self):
255 """Return the path with / as the path separator, regardless
261 return SCons.Util.SpecialAttrWrapper(string.replace(entry.get_path(),
263 entry.name + "_posix")
265 def __get_srcnode(self):
266 return EntryProxy(self.get().srcnode())
268 def __get_srcdir(self):
269 """Returns the directory containing the source node linked to this
270 node via BuildDir(), or the directory of this node if not linked."""
271 return EntryProxy(self.get().srcnode().dir)
274 return EntryProxy(self.get().dir)
276 dictSpecialAttrs = { "base" : __get_base_path,
277 "posix" : __get_posix_path,
278 "srcpath" : __get_srcnode,
279 "srcdir" : __get_srcdir,
281 "abspath" : __get_abspath,
282 "filebase" : __get_filebase,
283 "suffix" : __get_suffix,
284 "file" : __get_file }
286 def __getattr__(self, name):
287 # This is how we implement the "special" attributes
288 # such as base, posix, srcdir, etc.
290 return self.dictSpecialAttrs[name](self)
292 return SCons.Util.Proxy.__getattr__(self, name)
294 class Entry(SCons.Node.Node):
295 """A generic class for file system entries. This class is for
296 when we don't know yet whether the entry being looked up is a file
297 or a directory. Instances of this class can morph into either
298 Dir or File objects by a later, more precise lookup.
300 Note: this class does not define __cmp__ and __hash__ for efficiency
301 reasons. SCons does a lot of comparing of Entry objects, and so that
302 operation must be as fast as possible, which means we want to use
303 Python's built-in object identity comparison.
306 def __init__(self, name, directory, fs):
307 """Initialize a generic file system Entry.
309 Call the superclass initialization, take care of setting up
310 our relative and absolute paths, identify our parent
311 directory, and indicate that this node should use
313 SCons.Node.Node.__init__(self)
319 assert directory, "A directory must be provided"
321 self.abspath = directory.abspath_ + name
322 if directory.path == '.':
325 self.path = directory.path_ + name
327 self.path_ = self.path
328 self.abspath_ = self.abspath
330 self.cwd = None # will hold the SConscript directory for target nodes
331 self.duplicate = directory.duplicate
334 """Completely clear an Entry of all its cached state (so that it
335 can be re-evaluated by interfaces that do continuous integration
338 SCons.Node.Node.clear(self)
340 delattr(self, '_exists')
341 except AttributeError:
344 delattr(self, '_rexists')
345 except AttributeError:
352 """A FS node's string representation is its path name."""
353 if self.duplicate or self.has_builder():
354 return self.get_path()
355 return self.srcnode().get_path()
357 def get_contents(self):
358 """Fetch the contents of the entry.
360 Since this should return the real contents from the file
361 system, we check to see into what sort of subclass we should
363 if os.path.isfile(self.abspath):
364 self.__class__ = File
366 return File.get_contents(self)
367 if os.path.isdir(self.abspath):
370 return Dir.get_contents(self)
376 except AttributeError:
377 self._exists = _existsp(self.abspath)
381 if not hasattr(self, '_rexists'):
382 self._rexists = self.rfile().exists()
385 def get_parents(self):
386 parents = SCons.Node.Node.get_parents(self)
387 if self.dir and not isinstance(self.dir, ParentOfRoot):
388 parents.append(self.dir)
391 def current(self, calc):
392 """If the underlying path doesn't exist, we know the node is
393 not current without even checking the signature, so return 0.
394 Otherwise, return None to indicate that signature calculation
395 should proceed as normal to find out if the node is current."""
396 bsig = calc.bsig(self)
397 if not self.exists():
399 return calc.current(self, bsig)
401 def is_under(self, dir):
405 return self.dir.is_under(dir)
411 """If this node is in a build path, return the node
412 corresponding to its source file. Otherwise, return
416 except AttributeError:
421 self._srcnode = self.fs.Entry(name, dir.srcdir,
422 klass=self.__class__)
424 name = dir.name + os.sep + name
429 def recurse_get_path(self, dir, path_elems):
430 """Recursively build a path relative to a supplied directory
433 path_elems.append(self.name)
434 path_elems = self.dir.recurse_get_path(dir, path_elems)
437 def get_path(self, dir=None):
438 """Return path relative to the current working directory of the
439 FS object that owns us."""
441 dir = self.fs.getcwd()
443 return self.relpath[dir]
446 # Special case, return "." as the path
449 path_elems = self.recurse_get_path(dir, [])
451 ret = string.join(path_elems, os.sep)
452 self.relpath[dir] = ret
455 def set_src_builder(self, builder):
456 """Set the source code builder for this node."""
457 self.sbuilder = builder
459 def src_builder(self):
460 """Fetch the source code builder for this node.
462 If there isn't one, we cache the source code builder specified
463 for the directory (which in turn will cache the value from its
464 parent directory, and so on up to the file system root).
468 except AttributeError:
469 scb = self.dir.src_builder()
473 def get_abspath(self):
474 """Get the absolute path of the file."""
477 def for_signature(self):
478 # Return just our name. Even an absolute path would not work,
479 # because that can change thanks to symlinks or remapped network
483 def get_subst_proxy(self):
486 except AttributeError:
487 ret = EntryProxy(self)
491 # This is for later so we can differentiate between Entry the class and Entry
492 # the method of the FS class.
497 def __init__(self, path = None):
498 """Initialize the Node.FS subsystem.
500 The supplied path is the top of the source tree, where we
501 expect to find the top-level build file. If no path is
502 supplied, the current directory is the default.
504 The path argument must be a valid absolute path.
507 self.pathTop = os.getcwd()
512 self.SConstruct_dir = None
513 self.CachePath = None
514 self.cache_force = None
515 self.cache_show = None
517 def set_toplevel_dir(self, path):
518 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."
521 def set_SConstruct_dir(self, dir):
522 self.SConstruct_dir = dir
524 def __setTopLevelDir(self):
526 self.Top = self.__doLookup(Dir, os.path.normpath(self.pathTop))
528 self.Top.path_ = '.' + os.sep
532 self.__setTopLevelDir()
535 def __checkClass(self, node, klass):
538 if node.__class__ == Entry:
539 node.__class__ = klass
542 if not isinstance(node, klass):
543 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
544 (node.__class__.__name__, node.path, klass.__name__)
547 def __doLookup(self, fsclass, name, directory = None, create = 1):
548 """This method differs from the File and Dir factory methods in
549 one important way: the meaning of the directory parameter.
550 In this method, if directory is None or not supplied, the supplied
551 name is expected to be an absolute path. If you try to look up a
552 relative path with directory=None, then an AssertionError will be
556 # This is a stupid hack to compensate for the fact
557 # that the POSIX and Win32 versions of os.path.normpath()
558 # behave differently. In particular, in POSIX:
559 # os.path.normpath('./') == '.'
561 # os.path.normpath('./') == ''
562 # os.path.normpath('.\\') == ''
564 # This is a definite bug in the Python library, but we have
567 path_comp = string.split(name, os.sep)
568 drive, path_first = os.path.splitdrive(path_comp[0])
571 drive = _my_normcase(drive)
573 directory = self.Root[drive]
576 raise SCons.Errors.UserError
577 dir = Dir(drive, ParentOfRoot(), self)
578 dir.path = dir.path + os.sep
579 dir.abspath = dir.abspath + os.sep
580 self.Root[drive] = dir
582 path_comp = path_comp[1:]
584 path_comp = [ path_first, ] + path_comp[1:]
586 # Lookup the directory
587 for path_name in path_comp[:-1]:
588 path_norm = _my_normcase(path_name)
590 directory = self.__checkClass(directory.entries[path_norm],
594 raise SCons.Errors.UserError
596 # look at the actual filesystem and make sure there isn't
597 # a file already there
598 path = directory.path_ + path_name
599 if os.path.isfile(path):
601 "File %s found where directory expected." % path
603 dir_temp = Dir(path_name, directory, self)
604 directory.entries[path_norm] = dir_temp
605 directory.add_wkid(dir_temp)
607 file_name = _my_normcase(path_comp[-1])
609 ret = self.__checkClass(directory.entries[file_name], fsclass)
612 raise SCons.Errors.UserError
614 # make sure we don't create File nodes when there is actually
615 # a directory at that path on the disk, and vice versa
616 path = directory.path_ + path_comp[-1]
618 if os.path.isdir(path):
620 "Directory %s found where file expected." % path
622 if os.path.isfile(path):
624 "File %s found where directory expected." % path
626 ret = fsclass(path_comp[-1], directory, self)
627 directory.entries[file_name] = ret
628 directory.add_wkid(ret)
631 def __transformPath(self, name, directory):
632 """Take care of setting up the correct top-level directory,
633 usually in preparation for a call to doLookup().
635 If the path name is prepended with a '#', then it is unconditionally
636 interpreted as relative to the top-level directory of this FS.
638 If directory is None, and name is a relative path,
639 then the same applies.
641 self.__setTopLevelDir()
642 if name and name[0] == '#':
645 if name and (name[0] == os.sep or name[0] == '/'):
646 # Correct such that '#/foo' is equivalent
649 name = os.path.join('.', os.path.normpath(name))
651 directory = self._cwd
652 return (os.path.normpath(name), directory)
654 def chdir(self, dir, change_os_dir=0):
655 """Change the current working directory for lookups.
656 If change_os_dir is true, we will also change the "real" cwd
659 self.__setTopLevelDir()
665 os.chdir(dir.abspath)
670 def Entry(self, name, directory = None, create = 1, klass=None):
671 """Lookup or create a generic Entry node with the specified name.
672 If the name is a relative path (begins with ./, ../, or a file
673 name), then it is looked up relative to the supplied directory
674 node, or to the top level directory of the FS (supplied at
675 construction time) if no directory is supplied.
681 if isinstance(name, Entry):
682 return self.__checkClass(name, klass)
684 if directory and not isinstance(directory, Dir):
685 directory = self.Dir(directory)
686 name, directory = self.__transformPath(name, directory)
687 return self.__doLookup(klass, name, directory, create)
689 def File(self, name, directory = None, create = 1):
690 """Lookup or create a File node with the specified name. If
691 the name is a relative path (begins with ./, ../, or a file name),
692 then it is looked up relative to the supplied directory node,
693 or to the top level directory of the FS (supplied at construction
694 time) if no directory is supplied.
696 This method will raise TypeError if a directory is found at the
700 return self.Entry(name, directory, create, File)
702 def Dir(self, name, directory = None, create = 1):
703 """Lookup or create a Dir node with the specified name. If
704 the name is a relative path (begins with ./, ../, or a file name),
705 then it is looked up relative to the supplied directory node,
706 or to the top level directory of the FS (supplied at construction
707 time) if no directory is supplied.
709 This method will raise TypeError if a normal file is found at the
713 return self.Entry(name, directory, create, Dir)
715 def BuildDir(self, build_dir, src_dir, duplicate=1):
716 """Link the supplied build directory to the source directory
717 for purposes of building files."""
719 self.__setTopLevelDir()
720 if not isinstance(src_dir, SCons.Node.Node):
721 src_dir = self.Dir(src_dir)
722 if not isinstance(build_dir, SCons.Node.Node):
723 build_dir = self.Dir(build_dir)
724 if not src_dir.is_under(self.Top):
725 raise SCons.Errors.UserError, "Source directory must be under top of build tree."
726 if src_dir.is_under(build_dir):
727 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
728 build_dir.link(src_dir, duplicate)
730 def Repository(self, *dirs):
731 """Specify Repository directories to search."""
733 if not isinstance(d, SCons.Node.Node):
735 self.__setTopLevelDir()
736 self.Top.addRepository(d)
738 def Rsearch(self, path, clazz=_classEntry, cwd=None):
739 """Search for something in a Repository. Returns the first
740 one found in the list, or None if there isn't one."""
741 if isinstance(path, SCons.Node.Node):
744 name, d = self.__transformPath(path, cwd)
745 n = self.__doLookup(clazz, name, d)
748 if isinstance(n, Dir):
749 # If n is a Directory that has Repositories directly
750 # attached to it, then any of those is a valid Repository
751 # path. Return the first one that exists.
752 reps = filter(lambda x: x.exists(), n.getRepositories())
757 # Search repositories of all directories that this file is under.
759 for rep in d.getRepositories():
761 rnode = self.__doLookup(clazz, name, rep)
762 # Only find the node if it exists and it is not
763 # a derived file. If for some reason, we are
764 # explicitly building a file IN a Repository, we
765 # don't want it to show up in the build tree.
766 # This is usually the case with BuildDir().
767 # We only want to find pre-existing files.
768 if rnode.exists() and \
769 (isinstance(rnode, Dir) or not rnode.has_builder()):
772 pass # Wrong type of node.
773 # Prepend directory name
774 name = d.name + os.sep + name
775 # Go up one directory
779 def Rsearchall(self, pathlist, must_exist=1, clazz=_classEntry, cwd=None):
780 """Search for a list of somethings in the Repository list."""
782 if SCons.Util.is_String(pathlist):
783 pathlist = string.split(pathlist, os.pathsep)
784 if not SCons.Util.is_List(pathlist):
785 pathlist = [pathlist]
786 for path in pathlist:
787 if isinstance(path, SCons.Node.Node):
790 name, d = self.__transformPath(path, cwd)
791 n = self.__doLookup(clazz, name, d)
792 if not must_exist or n.exists():
794 if isinstance(n, Dir):
795 # If this node is a directory, then any repositories
796 # attached to this node can be repository paths.
797 ret.extend(filter(lambda x, me=must_exist, clazz=clazz: isinstance(x, clazz) and (not me or x.exists()),
798 n.getRepositories()))
802 # Search repositories of all directories that this file
805 for rep in d.getRepositories():
807 rnode = self.__doLookup(clazz, name, rep)
808 # Only find the node if it exists (or
809 # must_exist is zero) and it is not a
810 # derived file. If for some reason, we
811 # are explicitly building a file IN a
812 # Repository, we don't want it to show up in
813 # the build tree. This is usually the case
814 # with BuildDir(). We only want to find
815 # pre-existing files.
816 if (not must_exist or rnode.exists()) and \
817 (not rnode.has_builder() or isinstance(rnode, Dir)):
820 pass # Wrong type of node.
821 # Prepend directory name
822 name = d.name + os.sep + name
823 # Go up one directory
827 def CacheDir(self, path):
828 self.CachePath = path
830 def build_dir_target_climb(self, dir, tail):
831 """Create targets in corresponding build directories
833 Climb the directory tree, and look up path names
834 relative to any linked build directories we find.
839 for bd in dir.build_dirs:
840 p = apply(os.path.join, [bd.path] + tail)
841 targets.append(self.Entry(p))
842 tail = [dir.name] + tail
845 message = "building associated BuildDir targets: %s" % string.join(map(str, targets))
846 return targets, message
849 # Annotate with the creator
855 """A class for directories in a file system.
858 def __init__(self, name, directory, fs):
859 Entry.__init__(self, name, directory, fs)
863 """Turn a file system node (either a freshly initialized
864 directory object or a separate Entry object) into a
865 proper directory object.
867 Modify our paths to add the trailing slash that indicates
868 a directory. Set up this directory's entries and hook it
869 into the file system tree. Specify that directories (this
870 node) don't use signatures for currency calculation."""
872 self.path_ = self.path + os.sep
873 self.abspath_ = self.abspath + os.sep
874 self.repositories = []
878 self.entries['.'] = self
879 self.entries['..'] = self.dir
882 self._sconsign = None
885 def __clearRepositoryCache(self, duplicate=None):
886 """Called when we change the repository(ies) for a directory.
887 This clears any cached information that is invalidated by changing
890 for node in self.entries.values():
892 if node != self and isinstance(node, Dir):
893 node.__clearRepositoryCache(duplicate)
897 except AttributeError:
901 except AttributeError:
905 except AttributeError:
909 except AttributeError:
913 except AttributeError:
915 if duplicate != None:
916 node.duplicate=duplicate
918 def __resetDuplicate(self, node):
920 node.duplicate = node.get_dir().duplicate
922 def Entry(self, name):
923 """Create an entry node named 'name' relative to this directory."""
924 return self.fs.Entry(name, self)
927 """Create a directory node named 'name' relative to this directory."""
928 return self.fs.Dir(name, self)
930 def File(self, name):
931 """Create a file node named 'name' relative to this directory."""
932 return self.fs.File(name, self)
934 def link(self, srcdir, duplicate):
935 """Set this directory as the build directory for the
936 supplied source directory."""
938 self.duplicate = duplicate
939 self.__clearRepositoryCache(duplicate)
940 srcdir.build_dirs.append(self)
942 def getRepositories(self):
943 """Returns a list of repositories for this directory."""
944 if self.srcdir and not self.duplicate:
947 except AttributeError:
948 self._srcreps = self.fs.Rsearchall(self.srcdir.path,
954 return self.repositories
956 def addRepository(self, dir):
957 if not dir in self.repositories and dir != self:
958 self.repositories.append(dir)
959 self.__clearRepositoryCache()
962 return self.entries['..']
965 if not self.entries['..']:
968 return self.entries['..'].root()
970 def all_children(self, scan):
971 keys = filter(lambda k: k != '.' and k != '..', self.entries.keys())
972 kids = map(lambda x, s=self: s.entries[x], keys)
974 if one.abspath < two.abspath:
976 if one.abspath > two.abspath:
980 return kids + SCons.Node.Node.all_children(self, 0)
982 def get_actions(self):
983 """A null "builder" for directories."""
987 """A null "builder" for directories."""
990 def alter_targets(self):
991 """Return any corresponding targets in a build directory.
993 return self.fs.build_dir_target_climb(self, [])
995 def calc_signature(self, calc):
996 """A directory has no signature."""
999 def set_bsig(self, bsig):
1000 """A directory has no signature."""
1003 def set_csig(self, csig):
1004 """A directory has no signature."""
1007 def get_contents(self):
1008 """Return a fixed "contents" value of a directory."""
1014 def current(self, calc):
1015 """If all of our children were up-to-date, then this
1016 directory was up-to-date, too."""
1018 for kid in self.children(None):
1020 if s and (not state or s > state):
1023 if state == 0 or state == SCons.Node.up_to_date:
1031 except AttributeError:
1033 if not self.exists():
1034 n = self.fs.Rsearch(self.path, clazz=Dir, cwd=self.fs.Top)
1040 """Return the .sconsign file info for this directory,
1041 creating it first if necessary."""
1042 if not self._sconsign:
1044 self._sconsign = SCons.Sig.SConsignFile(self)
1045 return self._sconsign
1048 """Dir has a special need for srcnode()...if we
1049 have a srcdir attribute set, then that *is* our srcnode."""
1052 return Entry.srcnode(self)
1062 """A class for files in a file system.
1064 def __init__(self, name, directory, fs):
1065 Entry.__init__(self, name, directory, fs)
1068 def Entry(self, name):
1069 """Create an entry node named 'name' relative to
1070 the SConscript directory of this file."""
1071 return self.fs.Entry(name, self.cwd)
1073 def Dir(self, name):
1074 """Create a directory node named 'name' relative to
1075 the SConscript directory of this file."""
1076 return self.fs.Dir(name, self.cwd)
1078 def File(self, name):
1079 """Create a file node named 'name' relative to
1080 the SConscript directory of this file."""
1081 return self.fs.File(name, self.cwd)
1083 def RDirs(self, pathlist):
1084 """Search for a list of directories in the Repository list."""
1085 return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
1088 def generate_build_env(self, env):
1089 """Generate an appropriate Environment to build this File."""
1090 return env.Override({'Dir' : self.Dir,
1092 'RDirs' : self.RDirs})
1095 """Turn a file system node into a File object."""
1096 self.scanner_paths = {}
1097 self.found_includes = {}
1098 if not hasattr(self, '_local'):
1102 return self.dir.root()
1104 def get_contents(self):
1105 if not self.rexists():
1107 return open(self.rfile().abspath, "rb").read()
1109 def get_timestamp(self):
1111 return os.path.getmtime(self.rfile().abspath)
1115 def calc_signature(self, calc, cache=None):
1117 Select and calculate the appropriate build signature for a File.
1119 self - the File node
1120 calc - the signature calculation module
1121 cache - alternate node to use for the signature cache
1122 returns - the signature
1125 if self.has_builder():
1126 if SCons.Sig.build_signature:
1127 return calc.bsig(self.rfile(), self)
1129 return calc.csig(self.rfile(), self)
1130 elif not self.rexists():
1133 return calc.csig(self.rfile(), self)
1135 def store_csig(self):
1136 self.dir.sconsign().set_csig(self.name, self.get_csig())
1138 def store_bsig(self):
1139 self.dir.sconsign().set_bsig(self.name, self.get_bsig())
1141 def store_implicit(self):
1142 self.dir.sconsign().set_implicit(self.name, self.implicit)
1144 def store_timestamp(self):
1145 self.dir.sconsign().set_timestamp(self.name, self.get_timestamp())
1147 def get_prevsiginfo(self):
1148 """Fetch the previous signature information from the
1150 return self.dir.sconsign().get(self.name)
1152 def get_stored_implicit(self):
1153 return self.dir.sconsign().get_implicit(self.name)
1155 def get_found_includes(self, env, scanner, target):
1156 """Return the included implicit dependencies in this file.
1157 Cache results so we only scan the file once regardless of
1158 how many times this information is requested."""
1163 path = target.scanner_paths[scanner]
1164 except AttributeError:
1165 # The target had no scanner_paths attribute, which means
1166 # it's an Alias or some other node that's not actually a
1167 # file. In that case, back off and use the path for this
1170 path = self.scanner_paths[scanner]
1172 path = scanner.path(env, self.cwd)
1173 self.scanner_paths[scanner] = path
1175 path = scanner.path(env, target.cwd)
1176 target.scanner_paths[scanner] = path
1179 includes = self.found_includes[path]
1181 includes = scanner(self, env, path)
1182 self.found_includes[path] = includes
1186 def scanner_key(self):
1187 return os.path.splitext(self.name)[1]
1189 def _createDir(self):
1190 # ensure that the directories for this node are
1198 listDirs.append(parent)
1200 if isinstance(p, ParentOfRoot):
1201 raise SCons.Errors.StopError, parent.path
1204 for dirnode in listDirs:
1206 Mkdir(dirnode, None, None)
1207 # The Mkdir() action may or may not have actually
1208 # created the directory, depending on whether the -n
1209 # option was used or not. Delete the _exists and
1210 # _rexists attributes so they can be reevaluated.
1211 if hasattr(dirnode, '_exists'):
1212 delattr(dirnode, '_exists')
1213 if hasattr(dirnode, '_rexists'):
1214 delattr(dirnode, '_rexists')
1219 """Actually build the file.
1221 This overrides the base class build() method to check for the
1222 existence of derived files in a CacheDir before going ahead and
1225 This method is called from multiple threads in a parallel build,
1226 so only do thread safe stuff here. Do thread unsafe stuff in
1229 b = self.has_builder()
1230 if not b and not self.has_src_builder():
1232 if b and self.fs.CachePath:
1233 if self.fs.cache_show:
1234 if CacheRetrieveSilent(self, None, None) == 0:
1235 def do_print(action, targets, sources, env, self=self):
1236 al = action.strfunction(targets, self.sources, env)
1237 if not SCons.Util.is_List(al):
1241 self._for_each_action(do_print)
1243 elif CacheRetrieve(self, None, None) == 0:
1245 SCons.Node.Node.build(self)
1248 """Called just after this node is sucessfully built."""
1249 # Push this file out to cache before the superclass Node.built()
1250 # method has a chance to clear the build signature, which it
1251 # will do if this file has a source scanner.
1252 if self.fs.CachePath and os.path.exists(self.path):
1253 CachePush(self, None, None)
1254 SCons.Node.Node.built(self)
1255 self.found_includes = {}
1256 if hasattr(self, '_exists'):
1257 delattr(self, '_exists')
1258 if hasattr(self, '_rexists'):
1259 delattr(self, '_rexists')
1262 if self.fs.CachePath and self.fs.cache_force and os.path.exists(self.path):
1263 CachePush(self, None, None)
1265 def has_src_builder(self):
1266 """Return whether this Node has a source builder or not.
1268 If this Node doesn't have an explicit source code builder, this
1269 is where we figure out, on the fly, if there's a transparent
1270 source code builder for it.
1272 Note that if we found a source builder, we also set the
1273 self.builder attribute, so that all of the methods that actually
1274 *build* this file don't have to do anything different.
1278 except AttributeError:
1282 scb = self.dir.src_builder()
1286 sccspath = os.path.join('SCCS', 's.' + self.name)
1288 sccspath = os.path.join(dir, sccspath)
1289 if os.path.exists(sccspath):
1290 scb = get_DefaultSCCSBuilder()
1292 rcspath = os.path.join('RCS', self.name + ',v')
1294 rcspath = os.path.join(dir, rcspath)
1295 if os.path.exists(rcspath):
1296 scb = get_DefaultRCSBuilder()
1299 return not scb is None
1301 def is_derived(self):
1302 """Return whether this file is a derived file or not.
1304 This overrides the base class method to account for the fact
1305 that a file may be derived transparently from a source code
1308 return self.has_builder() or self.side_effect or self.has_src_builder()
1310 def alter_targets(self):
1311 """Return any corresponding targets in a build directory.
1313 if self.has_builder():
1315 return self.fs.build_dir_target_climb(self.dir, [self.name])
1318 """Prepare for this file to be created."""
1320 SCons.Node.Node.prepare(self)
1322 if self.get_state() != SCons.Node.up_to_date:
1324 if self.has_builder() and not self.precious:
1326 Unlink(self, None, None)
1328 raise SCons.Errors.BuildError(node = self,
1329 errstr = e.strerror)
1330 if hasattr(self, '_exists'):
1331 delattr(self, '_exists')
1335 except SCons.Errors.StopError, drive:
1336 desc = "No drive `%s' for target `%s'." % (drive, self)
1337 raise SCons.Errors.StopError, desc
1340 """Remove this file."""
1341 if _existsp(self.path):
1342 os.unlink(self.path)
1347 # Duplicate from source path if we are set up to do this.
1348 if self.duplicate and not self.has_builder() and not self.linked:
1349 src=self.srcnode().rfile()
1350 if src.exists() and src.abspath != self.abspath:
1353 Unlink(self, None, None)
1357 Link(self, src, None)
1359 desc = "Cannot duplicate `%s' in `%s': %s." % (src, self.dir, e.strerror)
1360 raise SCons.Errors.StopError, desc
1362 # The Link() action may or may not have actually
1363 # created the file, depending on whether the -n
1364 # option was used or not. Delete the _exists and
1365 # _rexists attributes so they can be reevaluated.
1366 if hasattr(self, '_exists'):
1367 delattr(self, '_exists')
1368 if hasattr(self, '_rexists'):
1369 delattr(self, '_rexists')
1370 return Entry.exists(self)
1372 def current(self, calc):
1373 bsig = calc.bsig(self)
1374 if not self.exists():
1375 # The file doesn't exist locally...
1378 # ...but there is one in a Repository...
1379 if calc.current(r, bsig):
1380 # ...and it's even up-to-date...
1382 # ...and they'd like a local copy.
1383 LocalCopy(self, r, None)
1390 return calc.current(self, bsig)
1393 if not hasattr(self, '_rfile'):
1395 if not self.exists():
1396 n = self.fs.Rsearch(self.path, clazz=File,
1403 return str(self.rfile())
1405 def cachepath(self):
1406 if self.fs.CachePath:
1407 bsig = self.get_bsig()
1409 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1411 subdir = string.upper(bsig[0])
1412 dir = os.path.join(self.fs.CachePath, subdir)
1413 return dir, os.path.join(dir, bsig)
1419 def find_file(filename, paths, node_factory = default_fs.File):
1421 find_file(str, [Dir()]) -> [nodes]
1423 filename - a filename to find
1424 paths - a list of directory path *nodes* to search in
1426 returns - the node created from the found file.
1428 Find a node corresponding to either a derived file or a file
1429 that exists already.
1431 Only the first file found is returned, and none is returned
1432 if no file is found.
1437 node = node_factory(filename, dir)
1438 # Return true of the node exists or is a derived node.
1439 if node.has_builder() or \
1440 (isinstance(node, SCons.Node.FS.Entry) and node.exists()):
1444 # If we find a directory instead of a file, we don't care
1449 def find_files(filenames, paths, node_factory = default_fs.File):
1451 find_files([str], [Dir()]) -> [nodes]
1453 filenames - a list of filenames to find
1454 paths - a list of directory path *nodes* to search in
1456 returns - the nodes created from the found files.
1458 Finds nodes corresponding to either derived files or files
1461 Only the first file found is returned for each filename,
1462 and any files that aren't found are ignored.
1464 nodes = map(lambda x, paths=paths, node_factory=node_factory:
1465 find_file(x, paths, node_factory),
1467 return filter(lambda x: x != None, nodes)