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 __init__(self, entry):
229 SCons.Util.Proxy.__init__(self, entry)
230 self.abspath = SCons.Util.SpecialAttrWrapper(entry.abspath,
231 entry.name + "_abspath")
232 filebase, suffix = os.path.splitext(entry.name)
233 self.filebase = SCons.Util.SpecialAttrWrapper(filebase,
234 entry.name + "_filebase")
235 self.suffix = SCons.Util.SpecialAttrWrapper(suffix,
236 entry.name + "_suffix")
237 self.file = SCons.Util.SpecialAttrWrapper(entry.name,
238 entry.name + "_file")
240 def __get_base_path(self):
241 """Return the file's directory and file name, with the
243 return SCons.Util.SpecialAttrWrapper(os.path.splitext(self.get().get_path())[0],
244 self.get().name + "_base")
246 def __get_posix_path(self):
247 """Return the path with / as the path separator, regardless
252 return SCons.Util.SpecialAttrWrapper(string.replace(self.get().get_path(),
254 self.get().name + "_posix")
256 def __get_srcnode(self):
257 return EntryProxy(self.get().srcnode())
259 def __get_srcdir(self):
260 """Returns the directory containing the source node linked to this
261 node via BuildDir(), or the directory of this node if not linked."""
262 return EntryProxy(self.get().srcnode().dir)
265 return EntryProxy(self.get().dir)
267 dictSpecialAttrs = { "base" : __get_base_path,
268 "posix" : __get_posix_path,
269 "srcpath" : __get_srcnode,
270 "srcdir" : __get_srcdir,
273 def __getattr__(self, name):
274 # This is how we implement the "special" attributes
275 # such as base, posix, srcdir, etc.
277 return self.dictSpecialAttrs[name](self)
279 return SCons.Util.Proxy.__getattr__(self, name)
282 class Entry(SCons.Node.Node):
283 """A generic class for file system entries. This class is for
284 when we don't know yet whether the entry being looked up is a file
285 or a directory. Instances of this class can morph into either
286 Dir or File objects by a later, more precise lookup.
288 Note: this class does not define __cmp__ and __hash__ for efficiency
289 reasons. SCons does a lot of comparing of Entry objects, and so that
290 operation must be as fast as possible, which means we want to use
291 Python's built-in object identity comparison.
294 def __init__(self, name, directory, fs):
295 """Initialize a generic file system Entry.
297 Call the superclass initialization, take care of setting up
298 our relative and absolute paths, identify our parent
299 directory, and indicate that this node should use
301 SCons.Node.Node.__init__(self)
307 assert directory, "A directory must be provided"
309 self.abspath = directory.abspath_ + name
310 if directory.path == '.':
313 self.path = directory.path_ + name
315 self.path_ = self.path
316 self.abspath_ = self.abspath
318 self.cwd = None # will hold the SConscript directory for target nodes
319 self.duplicate = directory.duplicate
322 """Completely clear an Entry of all its cached state (so that it
323 can be re-evaluated by interfaces that do continuous integration
326 SCons.Node.Node.clear(self)
328 delattr(self, '_exists')
329 except AttributeError:
332 delattr(self, '_rexists')
333 except AttributeError:
340 """A FS node's string representation is its path name."""
341 if self.duplicate or self.has_builder():
342 return self.get_path()
343 return self.srcnode().get_path()
345 def get_contents(self):
346 """Fetch the contents of the entry.
348 Since this should return the real contents from the file
349 system, we check to see into what sort of subclass we should
351 if os.path.isfile(self.abspath):
352 self.__class__ = File
354 return File.get_contents(self)
355 if os.path.isdir(self.abspath):
358 return Dir.get_contents(self)
364 except AttributeError:
365 self._exists = _existsp(self.abspath)
369 if not hasattr(self, '_rexists'):
370 self._rexists = self.rfile().exists()
373 def get_parents(self):
374 parents = SCons.Node.Node.get_parents(self)
375 if self.dir and not isinstance(self.dir, ParentOfRoot):
376 parents.append(self.dir)
379 def current(self, calc):
380 """If the underlying path doesn't exist, we know the node is
381 not current without even checking the signature, so return 0.
382 Otherwise, return None to indicate that signature calculation
383 should proceed as normal to find out if the node is current."""
384 bsig = calc.bsig(self)
385 if not self.exists():
387 return calc.current(self, bsig)
389 def is_under(self, dir):
393 return self.dir.is_under(dir)
399 """If this node is in a build path, return the node
400 corresponding to its source file. Otherwise, return
404 except AttributeError:
409 self._srcnode = self.fs.Entry(name, dir.srcdir,
410 klass=self.__class__)
412 name = dir.name + os.sep + name
417 def recurse_get_path(self, dir, path_elems):
418 """Recursively build a path relative to a supplied directory
421 path_elems.append(self.name)
422 path_elems = self.dir.recurse_get_path(dir, path_elems)
425 def get_path(self, dir=None):
426 """Return path relative to the current working directory of the
427 FS object that owns us."""
429 dir = self.fs.getcwd()
431 return self.relpath[dir]
434 # Special case, return "." as the path
437 path_elems = self.recurse_get_path(dir, [])
439 ret = string.join(path_elems, os.sep)
440 self.relpath[dir] = ret
443 def set_src_builder(self, builder):
444 """Set the source code builder for this node."""
445 self.sbuilder = builder
447 def src_builder(self):
448 """Fetch the source code builder for this node.
450 If there isn't one, we cache the source code builder specified
451 for the directory (which in turn will cache the value from its
452 parent directory, and so on up to the file system root).
456 except AttributeError:
457 scb = self.dir.src_builder()
461 def get_abspath(self):
462 """Get the absolute path of the file."""
465 def for_signature(self):
466 # Return just our name. Even an absolute path would not work,
467 # because that can change thanks to symlinks or remapped network
471 def get_subst_proxy(self):
474 except AttributeError:
475 ret = EntryProxy(self)
479 # This is for later so we can differentiate between Entry the class and Entry
480 # the method of the FS class.
485 def __init__(self, path = None):
486 """Initialize the Node.FS subsystem.
488 The supplied path is the top of the source tree, where we
489 expect to find the top-level build file. If no path is
490 supplied, the current directory is the default.
492 The path argument must be a valid absolute path.
495 self.pathTop = os.getcwd()
500 self.SConstruct_dir = None
501 self.CachePath = None
502 self.cache_force = None
503 self.cache_show = None
505 def set_toplevel_dir(self, path):
506 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."
509 def set_SConstruct_dir(self, dir):
510 self.SConstruct_dir = dir
512 def __setTopLevelDir(self):
514 self.Top = self.__doLookup(Dir, os.path.normpath(self.pathTop))
516 self.Top.path_ = '.' + os.sep
520 self.__setTopLevelDir()
523 def __checkClass(self, node, klass):
526 if node.__class__ == Entry:
527 node.__class__ = klass
530 if not isinstance(node, klass):
531 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
532 (node.__class__.__name__, node.path, klass.__name__)
535 def __doLookup(self, fsclass, name, directory = None, create = 1):
536 """This method differs from the File and Dir factory methods in
537 one important way: the meaning of the directory parameter.
538 In this method, if directory is None or not supplied, the supplied
539 name is expected to be an absolute path. If you try to look up a
540 relative path with directory=None, then an AssertionError will be
544 # This is a stupid hack to compensate for the fact
545 # that the POSIX and Win32 versions of os.path.normpath()
546 # behave differently. In particular, in POSIX:
547 # os.path.normpath('./') == '.'
549 # os.path.normpath('./') == ''
550 # os.path.normpath('.\\') == ''
552 # This is a definite bug in the Python library, but we have
555 path_comp = string.split(name, os.sep)
556 drive, path_first = os.path.splitdrive(path_comp[0])
559 drive = _my_normcase(drive)
561 directory = self.Root[drive]
564 raise SCons.Errors.UserError
565 dir = Dir(drive, ParentOfRoot(), self)
566 dir.path = dir.path + os.sep
567 dir.abspath = dir.abspath + os.sep
568 self.Root[drive] = dir
570 path_comp = path_comp[1:]
572 path_comp = [ path_first, ] + path_comp[1:]
574 # Lookup the directory
575 for path_name in path_comp[:-1]:
576 path_norm = _my_normcase(path_name)
578 directory = self.__checkClass(directory.entries[path_norm],
582 raise SCons.Errors.UserError
584 # look at the actual filesystem and make sure there isn't
585 # a file already there
586 path = directory.path_ + path_name
587 if os.path.isfile(path):
589 "File %s found where directory expected." % path
591 dir_temp = Dir(path_name, directory, self)
592 directory.entries[path_norm] = dir_temp
593 directory.add_wkid(dir_temp)
595 file_name = _my_normcase(path_comp[-1])
597 ret = self.__checkClass(directory.entries[file_name], fsclass)
600 raise SCons.Errors.UserError
602 # make sure we don't create File nodes when there is actually
603 # a directory at that path on the disk, and vice versa
604 path = directory.path_ + path_comp[-1]
606 if os.path.isdir(path):
608 "Directory %s found where file expected." % path
610 if os.path.isfile(path):
612 "File %s found where directory expected." % path
614 ret = fsclass(path_comp[-1], directory, self)
615 directory.entries[file_name] = ret
616 directory.add_wkid(ret)
619 def __transformPath(self, name, directory):
620 """Take care of setting up the correct top-level directory,
621 usually in preparation for a call to doLookup().
623 If the path name is prepended with a '#', then it is unconditionally
624 interpreted as relative to the top-level directory of this FS.
626 If directory is None, and name is a relative path,
627 then the same applies.
629 self.__setTopLevelDir()
630 if name and name[0] == '#':
633 if name and (name[0] == os.sep or name[0] == '/'):
634 # Correct such that '#/foo' is equivalent
637 name = os.path.join('.', os.path.normpath(name))
639 directory = self._cwd
640 return (os.path.normpath(name), directory)
642 def chdir(self, dir, change_os_dir=0):
643 """Change the current working directory for lookups.
644 If change_os_dir is true, we will also change the "real" cwd
647 self.__setTopLevelDir()
653 os.chdir(dir.abspath)
658 def Entry(self, name, directory = None, create = 1, klass=None):
659 """Lookup or create a generic Entry node with the specified name.
660 If the name is a relative path (begins with ./, ../, or a file
661 name), then it is looked up relative to the supplied directory
662 node, or to the top level directory of the FS (supplied at
663 construction time) if no directory is supplied.
669 if isinstance(name, Entry):
670 return self.__checkClass(name, klass)
672 if directory and not isinstance(directory, Dir):
673 directory = self.Dir(directory)
674 name, directory = self.__transformPath(name, directory)
675 return self.__doLookup(klass, name, directory, create)
677 def File(self, name, directory = None, create = 1):
678 """Lookup or create a File node with the specified name. If
679 the name is a relative path (begins with ./, ../, or a file name),
680 then it is looked up relative to the supplied directory node,
681 or to the top level directory of the FS (supplied at construction
682 time) if no directory is supplied.
684 This method will raise TypeError if a directory is found at the
688 return self.Entry(name, directory, create, File)
690 def Dir(self, name, directory = None, create = 1):
691 """Lookup or create a Dir node with the specified name. If
692 the name is a relative path (begins with ./, ../, or a file name),
693 then it is looked up relative to the supplied directory node,
694 or to the top level directory of the FS (supplied at construction
695 time) if no directory is supplied.
697 This method will raise TypeError if a normal file is found at the
701 return self.Entry(name, directory, create, Dir)
703 def BuildDir(self, build_dir, src_dir, duplicate=1):
704 """Link the supplied build directory to the source directory
705 for purposes of building files."""
707 self.__setTopLevelDir()
708 if not isinstance(src_dir, SCons.Node.Node):
709 src_dir = self.Dir(src_dir)
710 if not isinstance(build_dir, SCons.Node.Node):
711 build_dir = self.Dir(build_dir)
712 if not src_dir.is_under(self.Top):
713 raise SCons.Errors.UserError, "Source directory must be under top of build tree."
714 if src_dir.is_under(build_dir):
715 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
716 build_dir.link(src_dir, duplicate)
718 def Repository(self, *dirs):
719 """Specify Repository directories to search."""
721 if not isinstance(d, SCons.Node.Node):
723 self.__setTopLevelDir()
724 self.Top.addRepository(d)
726 def Rsearch(self, path, clazz=_classEntry, cwd=None):
727 """Search for something in a Repository. Returns the first
728 one found in the list, or None if there isn't one."""
729 if isinstance(path, SCons.Node.Node):
732 name, d = self.__transformPath(path, cwd)
733 n = self.__doLookup(clazz, name, d)
736 if isinstance(n, Dir):
737 # If n is a Directory that has Repositories directly
738 # attached to it, then any of those is a valid Repository
739 # path. Return the first one that exists.
740 reps = filter(lambda x: x.exists(), n.getRepositories())
745 # Search repositories of all directories that this file is under.
747 for rep in d.getRepositories():
749 rnode = self.__doLookup(clazz, name, rep)
750 # Only find the node if it exists and it is not
751 # a derived file. If for some reason, we are
752 # explicitly building a file IN a Repository, we
753 # don't want it to show up in the build tree.
754 # This is usually the case with BuildDir().
755 # We only want to find pre-existing files.
756 if rnode.exists() and \
757 (isinstance(rnode, Dir) or not rnode.has_builder()):
760 pass # Wrong type of node.
761 # Prepend directory name
762 name = d.name + os.sep + name
763 # Go up one directory
767 def Rsearchall(self, pathlist, must_exist=1, clazz=_classEntry, cwd=None):
768 """Search for a list of somethings in the Repository list."""
770 if SCons.Util.is_String(pathlist):
771 pathlist = string.split(pathlist, os.pathsep)
772 if not SCons.Util.is_List(pathlist):
773 pathlist = [pathlist]
774 for path in pathlist:
775 if isinstance(path, SCons.Node.Node):
778 name, d = self.__transformPath(path, cwd)
779 n = self.__doLookup(clazz, name, d)
780 if not must_exist or n.exists():
782 if isinstance(n, Dir):
783 # If this node is a directory, then any repositories
784 # attached to this node can be repository paths.
785 ret.extend(filter(lambda x, me=must_exist, clazz=clazz: isinstance(x, clazz) and (not me or x.exists()),
786 n.getRepositories()))
790 # Search repositories of all directories that this file
793 for rep in d.getRepositories():
795 rnode = self.__doLookup(clazz, name, rep)
796 # Only find the node if it exists (or
797 # must_exist is zero) and it is not a
798 # derived file. If for some reason, we
799 # are explicitly building a file IN a
800 # Repository, we don't want it to show up in
801 # the build tree. This is usually the case
802 # with BuildDir(). We only want to find
803 # pre-existing files.
804 if (not must_exist or rnode.exists()) and \
805 (not rnode.has_builder() or isinstance(rnode, Dir)):
808 pass # Wrong type of node.
809 # Prepend directory name
810 name = d.name + os.sep + name
811 # Go up one directory
815 def CacheDir(self, path):
816 self.CachePath = path
818 def build_dir_target_climb(self, dir, tail):
819 """Create targets in corresponding build directories
821 Climb the directory tree, and look up path names
822 relative to any linked build directories we find.
827 for bd in dir.build_dirs:
828 p = apply(os.path.join, [bd.path] + tail)
829 targets.append(self.Entry(p))
830 tail = [dir.name] + tail
833 message = "building associated BuildDir targets: %s" % string.join(map(str, targets))
834 return targets, message
837 # Annotate with the creator
843 """A class for directories in a file system.
846 def __init__(self, name, directory, fs):
847 Entry.__init__(self, name, directory, fs)
851 """Turn a file system node (either a freshly initialized
852 directory object or a separate Entry object) into a
853 proper directory object.
855 Modify our paths to add the trailing slash that indicates
856 a directory. Set up this directory's entries and hook it
857 into the file system tree. Specify that directories (this
858 node) don't use signatures for currency calculation."""
860 self.path_ = self.path + os.sep
861 self.abspath_ = self.abspath + os.sep
862 self.repositories = []
866 self.entries['.'] = self
867 self.entries['..'] = self.dir
870 self._sconsign = None
873 def __clearRepositoryCache(self, duplicate=None):
874 """Called when we change the repository(ies) for a directory.
875 This clears any cached information that is invalidated by changing
878 for node in self.entries.values():
880 if node != self and isinstance(node, Dir):
881 node.__clearRepositoryCache(duplicate)
885 except AttributeError:
889 except AttributeError:
893 except AttributeError:
897 except AttributeError:
901 except AttributeError:
903 if duplicate != None:
904 node.duplicate=duplicate
906 def __resetDuplicate(self, node):
908 node.duplicate = node.get_dir().duplicate
910 def Entry(self, name):
911 """Create an entry node named 'name' relative to this directory."""
912 return self.fs.Entry(name, self)
915 """Create a directory node named 'name' relative to this directory."""
916 return self.fs.Dir(name, self)
918 def File(self, name):
919 """Create a file node named 'name' relative to this directory."""
920 return self.fs.File(name, self)
922 def link(self, srcdir, duplicate):
923 """Set this directory as the build directory for the
924 supplied source directory."""
926 self.duplicate = duplicate
927 self.__clearRepositoryCache(duplicate)
928 srcdir.build_dirs.append(self)
930 def getRepositories(self):
931 """Returns a list of repositories for this directory."""
932 if self.srcdir and not self.duplicate:
935 except AttributeError:
936 self._srcreps = self.fs.Rsearchall(self.srcdir.path,
942 return self.repositories
944 def addRepository(self, dir):
945 if not dir in self.repositories and dir != self:
946 self.repositories.append(dir)
947 self.__clearRepositoryCache()
950 return self.entries['..']
953 if not self.entries['..']:
956 return self.entries['..'].root()
958 def all_children(self, scan):
959 keys = filter(lambda k: k != '.' and k != '..', self.entries.keys())
960 kids = map(lambda x, s=self: s.entries[x], keys)
962 if one.abspath < two.abspath:
964 if one.abspath > two.abspath:
968 return kids + SCons.Node.Node.all_children(self, 0)
970 def get_actions(self):
971 """A null "builder" for directories."""
975 """A null "builder" for directories."""
978 def alter_targets(self):
979 """Return any corresponding targets in a build directory.
981 return self.fs.build_dir_target_climb(self, [])
983 def calc_signature(self, calc):
984 """A directory has no signature."""
987 def set_bsig(self, bsig):
988 """A directory has no signature."""
991 def set_csig(self, csig):
992 """A directory has no signature."""
995 def get_contents(self):
996 """Return a fixed "contents" value of a directory."""
1002 def current(self, calc):
1003 """If all of our children were up-to-date, then this
1004 directory was up-to-date, too."""
1006 for kid in self.children(None):
1008 if s and (not state or s > state):
1011 if state == 0 or state == SCons.Node.up_to_date:
1019 except AttributeError:
1021 if not self.exists():
1022 n = self.fs.Rsearch(self.path, clazz=Dir, cwd=self.fs.Top)
1028 """Return the .sconsign file info for this directory,
1029 creating it first if necessary."""
1030 if not self._sconsign:
1032 self._sconsign = SCons.Sig.SConsignFile(self)
1033 return self._sconsign
1036 """Dir has a special need for srcnode()...if we
1037 have a srcdir attribute set, then that *is* our srcnode."""
1040 return Entry.srcnode(self)
1050 """A class for files in a file system.
1052 def __init__(self, name, directory, fs):
1053 Entry.__init__(self, name, directory, fs)
1056 def Entry(self, name):
1057 """Create an entry node named 'name' relative to
1058 the SConscript directory of this file."""
1059 return self.fs.Entry(name, self.cwd)
1061 def Dir(self, name):
1062 """Create a directory node named 'name' relative to
1063 the SConscript directory of this file."""
1064 return self.fs.Dir(name, self.cwd)
1066 def File(self, name):
1067 """Create a file node named 'name' relative to
1068 the SConscript directory of this file."""
1069 return self.fs.File(name, self.cwd)
1071 def RDirs(self, pathlist):
1072 """Search for a list of directories in the Repository list."""
1073 return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
1076 def generate_build_env(self, env):
1077 """Generate an appropriate Environment to build this File."""
1078 return env.Override({'Dir' : self.Dir,
1080 'RDirs' : self.RDirs})
1083 """Turn a file system node into a File object."""
1084 self.scanner_paths = {}
1085 self.found_includes = {}
1086 if not hasattr(self, '_local'):
1090 return self.dir.root()
1092 def get_contents(self):
1093 if not self.rexists():
1095 return open(self.rfile().abspath, "rb").read()
1097 def get_timestamp(self):
1099 return os.path.getmtime(self.rfile().abspath)
1103 def calc_signature(self, calc, cache=None):
1105 Select and calculate the appropriate build signature for a File.
1107 self - the File node
1108 calc - the signature calculation module
1109 cache - alternate node to use for the signature cache
1110 returns - the signature
1113 if self.has_builder():
1114 if SCons.Sig.build_signature:
1115 return calc.bsig(self.rfile(), self)
1117 return calc.csig(self.rfile(), self)
1118 elif not self.rexists():
1121 return calc.csig(self.rfile(), self)
1123 def store_csig(self):
1124 self.dir.sconsign().set_csig(self.name, self.get_csig())
1126 def store_bsig(self):
1127 self.dir.sconsign().set_bsig(self.name, self.get_bsig())
1129 def store_implicit(self):
1130 self.dir.sconsign().set_implicit(self.name, self.implicit)
1132 def store_timestamp(self):
1133 self.dir.sconsign().set_timestamp(self.name, self.get_timestamp())
1135 def get_prevsiginfo(self):
1136 """Fetch the previous signature information from the
1138 return self.dir.sconsign().get(self.name)
1140 def get_stored_implicit(self):
1141 return self.dir.sconsign().get_implicit(self.name)
1143 def get_found_includes(self, env, scanner, target):
1144 """Return the included implicit dependencies in this file.
1145 Cache results so we only scan the file once regardless of
1146 how many times this information is requested."""
1151 path = target.scanner_paths[scanner]
1152 except AttributeError:
1153 # The target had no scanner_paths attribute, which means
1154 # it's an Alias or some other node that's not actually a
1155 # file. In that case, back off and use the path for this
1158 path = self.scanner_paths[scanner]
1160 path = scanner.path(env, self.cwd)
1161 self.scanner_paths[scanner] = path
1163 path = scanner.path(env, target.cwd)
1164 target.scanner_paths[scanner] = path
1167 includes = self.found_includes[path]
1169 includes = scanner(self, env, path)
1170 self.found_includes[path] = includes
1174 def scanner_key(self):
1175 return os.path.splitext(self.name)[1]
1177 def _createDir(self):
1178 # ensure that the directories for this node are
1186 listDirs.append(parent)
1188 if isinstance(p, ParentOfRoot):
1189 raise SCons.Errors.StopError, parent.path
1192 for dirnode in listDirs:
1194 Mkdir(dirnode, None, None)
1195 # The Mkdir() action may or may not have actually
1196 # created the directory, depending on whether the -n
1197 # option was used or not. Delete the _exists and
1198 # _rexists attributes so they can be reevaluated.
1199 if hasattr(dirnode, '_exists'):
1200 delattr(dirnode, '_exists')
1201 if hasattr(dirnode, '_rexists'):
1202 delattr(dirnode, '_rexists')
1207 """Actually build the file.
1209 This overrides the base class build() method to check for the
1210 existence of derived files in a CacheDir before going ahead and
1213 This method is called from multiple threads in a parallel build,
1214 so only do thread safe stuff here. Do thread unsafe stuff in
1217 b = self.has_builder()
1218 if not b and not self.has_src_builder():
1220 if b and self.fs.CachePath:
1221 if self.fs.cache_show:
1222 if CacheRetrieveSilent(self, None, None) == 0:
1223 def do_print(action, targets, sources, env, self=self):
1224 al = action.strfunction(targets, self.sources, env)
1225 if not SCons.Util.is_List(al):
1229 self._for_each_action(do_print)
1231 elif CacheRetrieve(self, None, None) == 0:
1233 SCons.Node.Node.build(self)
1236 """Called just after this node is sucessfully built."""
1237 # Push this file out to cache before the superclass Node.built()
1238 # method has a chance to clear the build signature, which it
1239 # will do if this file has a source scanner.
1240 if self.fs.CachePath and os.path.exists(self.path):
1241 CachePush(self, None, None)
1242 SCons.Node.Node.built(self)
1243 self.found_includes = {}
1244 if hasattr(self, '_exists'):
1245 delattr(self, '_exists')
1246 if hasattr(self, '_rexists'):
1247 delattr(self, '_rexists')
1250 if self.fs.CachePath and self.fs.cache_force and os.path.exists(self.path):
1251 CachePush(self, None, None)
1253 def has_src_builder(self):
1254 """Return whether this Node has a source builder or not.
1256 If this Node doesn't have an explicit source code builder, this
1257 is where we figure out, on the fly, if there's a transparent
1258 source code builder for it.
1260 Note that if we found a source builder, we also set the
1261 self.builder attribute, so that all of the methods that actually
1262 *build* this file don't have to do anything different.
1266 except AttributeError:
1270 scb = self.dir.src_builder()
1274 sccspath = os.path.join('SCCS', 's.' + self.name)
1276 sccspath = os.path.join(dir, sccspath)
1277 if os.path.exists(sccspath):
1278 scb = get_DefaultSCCSBuilder()
1280 rcspath = os.path.join('RCS', self.name + ',v')
1282 rcspath = os.path.join(dir, rcspath)
1283 if os.path.exists(rcspath):
1284 scb = get_DefaultRCSBuilder()
1287 return not scb is None
1289 def is_derived(self):
1290 """Return whether this file is a derived file or not.
1292 This overrides the base class method to account for the fact
1293 that a file may be derived transparently from a source code
1296 return self.has_builder() or self.side_effect or self.has_src_builder()
1298 def alter_targets(self):
1299 """Return any corresponding targets in a build directory.
1301 if self.has_builder():
1303 return self.fs.build_dir_target_climb(self.dir, [self.name])
1306 """Prepare for this file to be created."""
1308 SCons.Node.Node.prepare(self)
1310 if self.get_state() != SCons.Node.up_to_date:
1312 if self.has_builder() and not self.precious:
1314 Unlink(self, None, None)
1316 raise SCons.Errors.BuildError(node = self,
1317 errstr = e.strerror)
1318 if hasattr(self, '_exists'):
1319 delattr(self, '_exists')
1323 except SCons.Errors.StopError, drive:
1324 desc = "No drive `%s' for target `%s'." % (drive, self)
1325 raise SCons.Errors.StopError, desc
1328 """Remove this file."""
1329 if _existsp(self.path):
1330 os.unlink(self.path)
1335 # Duplicate from source path if we are set up to do this.
1336 if self.duplicate and not self.has_builder() and not self.linked:
1337 src=self.srcnode().rfile()
1338 if src.exists() and src.abspath != self.abspath:
1341 Unlink(self, None, None)
1345 Link(self, src, None)
1347 desc = "Cannot duplicate `%s' in `%s': %s." % (src, self.dir, e.strerror)
1348 raise SCons.Errors.StopError, desc
1350 # The Link() action may or may not have actually
1351 # created the file, depending on whether the -n
1352 # option was used or not. Delete the _exists and
1353 # _rexists attributes so they can be reevaluated.
1354 if hasattr(self, '_exists'):
1355 delattr(self, '_exists')
1356 if hasattr(self, '_rexists'):
1357 delattr(self, '_rexists')
1358 return Entry.exists(self)
1360 def current(self, calc):
1361 bsig = calc.bsig(self)
1362 if not self.exists():
1363 # The file doesn't exist locally...
1366 # ...but there is one in a Repository...
1367 if calc.current(r, bsig):
1368 # ...and it's even up-to-date...
1370 # ...and they'd like a local copy.
1371 LocalCopy(self, r, None)
1378 return calc.current(self, bsig)
1381 if not hasattr(self, '_rfile'):
1383 if not self.exists():
1384 n = self.fs.Rsearch(self.path, clazz=File,
1391 return str(self.rfile())
1393 def cachepath(self):
1394 if self.fs.CachePath:
1395 bsig = self.get_bsig()
1397 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1399 subdir = string.upper(bsig[0])
1400 dir = os.path.join(self.fs.CachePath, subdir)
1401 return dir, os.path.join(dir, bsig)
1407 def find_file(filename, paths, node_factory = default_fs.File):
1409 find_file(str, [Dir()]) -> [nodes]
1411 filename - a filename to find
1412 paths - a list of directory path *nodes* to search in
1414 returns - the node created from the found file.
1416 Find a node corresponding to either a derived file or a file
1417 that exists already.
1419 Only the first file found is returned, and none is returned
1420 if no file is found.
1425 node = node_factory(filename, dir)
1426 # Return true of the node exists or is a derived node.
1427 if node.has_builder() or \
1428 (isinstance(node, SCons.Node.FS.Entry) and node.exists()):
1432 # If we find a directory instead of a file, we don't care
1437 def find_files(filenames, paths, node_factory = default_fs.File):
1439 find_files([str], [Dir()]) -> [nodes]
1441 filenames - a list of filenames to find
1442 paths - a list of directory path *nodes* to search in
1444 returns - the nodes created from the found files.
1446 Finds nodes corresponding to either derived files or files
1449 Only the first file found is returned for each filename,
1450 and any files that aren't found are ignored.
1452 nodes = map(lambda x, paths=paths, node_factory=node_factory:
1453 find_file(x, paths, node_factory),
1455 return filter(lambda x: x != None, nodes)