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__"
44 from UserDict import UserDict
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 not os.path.isdir(cachedir):
135 shutil.copy2(t.path, cachefile)
137 os.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
139 CachePush = SCons.Action.Action(CachePushFunc, None)
144 An instance of this class is used as the parent of the root of a
145 filesystem (POSIX) or drive (Win32). This isn't actually a node,
146 but it looks enough like one so that we don't have to have
147 special purpose code everywhere to deal with dir being None.
148 This class is an instance of the Null object pattern.
159 def is_under(self, dir):
165 def getRepositories(self):
171 def recurse_get_path(self, dir, path_elems):
174 def src_builder(self):
177 if os.path.normcase("TeSt") == os.path.normpath("TeSt"):
182 return string.upper(x)
184 class Entry(SCons.Node.Node):
185 """A generic class for file system entries. This class is for
186 when we don't know yet whether the entry being looked up is a file
187 or a directory. Instances of this class can morph into either
188 Dir or File objects by a later, more precise lookup.
190 Note: this class does not define __cmp__ and __hash__ for efficiency
191 reasons. SCons does a lot of comparing of Entry objects, and so that
192 operation must be as fast as possible, which means we want to use
193 Python's built-in object identity comparison.
196 def __init__(self, name, directory, fs):
197 """Initialize a generic file system Entry.
199 Call the superclass initialization, take care of setting up
200 our relative and absolute paths, identify our parent
201 directory, and indicate that this node should use
203 SCons.Node.Node.__init__(self)
209 assert directory, "A directory must be provided"
211 self.abspath = directory.abspath_ + name
212 if directory.path == '.':
215 self.path = directory.path_ + name
217 self.path_ = self.path
218 self.abspath_ = self.abspath
220 self.cwd = None # will hold the SConscript directory for target nodes
221 self.duplicate = directory.duplicate
227 """A FS node's string representation is its path name."""
228 if self.duplicate or self.has_builder():
229 return self.get_path()
230 return self.srcnode().get_path()
232 def get_contents(self):
233 """Fetch the contents of the entry.
235 Since this should return the real contents from the file
236 system, we check to see into what sort of subclass we should
238 if os.path.isfile(self.abspath):
239 self.__class__ = File
241 return File.get_contents(self)
242 if os.path.isdir(self.abspath):
245 return Dir.get_contents(self)
251 except AttributeError:
252 self._exists = os.path.exists(self.abspath)
256 if not hasattr(self, '_rexists'):
257 self._rexists = self.rfile().exists()
260 def get_parents(self):
261 parents = SCons.Node.Node.get_parents(self)
262 if self.dir and not isinstance(self.dir, ParentOfRoot):
263 parents.append(self.dir)
266 def current(self, calc):
267 """If the underlying path doesn't exist, we know the node is
268 not current without even checking the signature, so return 0.
269 Otherwise, return None to indicate that signature calculation
270 should proceed as normal to find out if the node is current."""
271 bsig = calc.bsig(self)
272 if not self.exists():
274 return calc.current(self, bsig)
276 def is_under(self, dir):
280 return self.dir.is_under(dir)
286 """If this node is in a build path, return the node
287 corresponding to its source file. Otherwise, return
291 except AttributeError:
296 self._srcnode = self.fs.Entry(name, dir.srcdir,
297 klass=self.__class__)
299 name = dir.name + os.sep + name
304 def recurse_get_path(self, dir, path_elems):
305 """Recursively build a path relative to a supplied directory
308 path_elems.append(self.name)
309 path_elems = self.dir.recurse_get_path(dir, path_elems)
312 def get_path(self, dir=None):
313 """Return path relative to the current working directory of the
314 FS object that owns us."""
316 dir = self.fs.getcwd()
318 return self.relpath[dir]
321 # Special case, return "." as the path
324 path_elems = self.recurse_get_path(dir, [])
326 ret = string.join(path_elems, os.sep)
327 self.relpath[dir] = ret
330 def set_src_builder(self, builder):
331 """Set the source code builder for this node."""
332 self.sbuilder = builder
334 def src_builder(self):
335 """Fetch the source code builder for this node.
337 If there isn't one, we cache the source code builder specified
338 for the directory (which in turn will cache the value from its
339 parent directory, and so on up to the file system root).
343 except AttributeError:
344 scb = self.dir.src_builder()
348 # This is for later so we can differentiate between Entry the class and Entry
349 # the method of the FS class.
354 def __init__(self, path = None):
355 """Initialize the Node.FS subsystem.
357 The supplied path is the top of the source tree, where we
358 expect to find the top-level build file. If no path is
359 supplied, the current directory is the default.
361 The path argument must be a valid absolute path.
364 self.pathTop = os.getcwd()
369 self.SConstruct = None
370 self.CachePath = None
371 self.cache_force = None
372 self.cache_show = None
374 def set_toplevel_dir(self, path):
375 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."
378 def set_SConstruct(self, path):
379 self.SConstruct = self.File(path)
381 def __setTopLevelDir(self):
383 self.Top = self.__doLookup(Dir, os.path.normpath(self.pathTop))
385 self.Top.path_ = '.' + os.sep
389 self.__setTopLevelDir()
392 def __checkClass(self, node, klass):
395 if node.__class__ == Entry:
396 node.__class__ = klass
399 if not isinstance(node, klass):
400 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
401 (node.__class__.__name__, node.path, klass.__name__)
404 def __doLookup(self, fsclass, name, directory = None, create = 1):
405 """This method differs from the File and Dir factory methods in
406 one important way: the meaning of the directory parameter.
407 In this method, if directory is None or not supplied, the supplied
408 name is expected to be an absolute path. If you try to look up a
409 relative path with directory=None, then an AssertionError will be
413 # This is a stupid hack to compensate for the fact
414 # that the POSIX and Win32 versions of os.path.normpath()
415 # behave differently. In particular, in POSIX:
416 # os.path.normpath('./') == '.'
418 # os.path.normpath('./') == ''
419 # os.path.normpath('.\\') == ''
421 # This is a definite bug in the Python library, but we have
424 path_comp = string.split(name, os.sep)
425 drive, path_first = os.path.splitdrive(path_comp[0])
428 drive = _my_normcase(drive)
430 directory = self.Root[drive]
433 raise SCons.Errors.UserError
434 dir = Dir(drive, ParentOfRoot(), self)
435 dir.path = dir.path + os.sep
436 dir.abspath = dir.abspath + os.sep
437 self.Root[drive] = dir
439 path_comp = path_comp[1:]
441 path_comp = [ path_first, ] + path_comp[1:]
443 # Lookup the directory
444 for path_name in path_comp[:-1]:
445 path_norm = _my_normcase(path_name)
447 directory = self.__checkClass(directory.entries[path_norm],
451 raise SCons.Errors.UserError
453 # look at the actual filesystem and make sure there isn't
454 # a file already there
455 path = directory.path_ + path_name
456 if os.path.isfile(path):
458 "File %s found where directory expected." % path
460 dir_temp = Dir(path_name, directory, self)
461 directory.entries[path_norm] = dir_temp
462 directory.add_wkid(dir_temp)
464 file_name = _my_normcase(path_comp[-1])
466 ret = self.__checkClass(directory.entries[file_name], fsclass)
469 raise SCons.Errors.UserError
471 # make sure we don't create File nodes when there is actually
472 # a directory at that path on the disk, and vice versa
473 path = directory.path_ + path_comp[-1]
475 if os.path.isdir(path):
477 "Directory %s found where file expected." % path
479 if os.path.isfile(path):
481 "File %s found where directory expected." % path
483 ret = fsclass(path_comp[-1], directory, self)
484 directory.entries[file_name] = ret
485 directory.add_wkid(ret)
488 def __transformPath(self, name, directory):
489 """Take care of setting up the correct top-level directory,
490 usually in preparation for a call to doLookup().
492 If the path name is prepended with a '#', then it is unconditionally
493 interpreted as relative to the top-level directory of this FS.
495 If directory is None, and name is a relative path,
496 then the same applies.
498 self.__setTopLevelDir()
499 if name and name[0] == '#':
502 if name and (name[0] == os.sep or name[0] == '/'):
503 # Correct such that '#/foo' is equivalent
506 name = os.path.join('.', os.path.normpath(name))
508 directory = self._cwd
509 return (os.path.normpath(name), directory)
511 def chdir(self, dir, change_os_dir=0):
512 """Change the current working directory for lookups.
513 If change_os_dir is true, we will also change the "real" cwd
516 self.__setTopLevelDir()
520 os.chdir(dir.abspath)
522 def Entry(self, name, directory = None, create = 1, klass=None):
523 """Lookup or create a generic Entry node with the specified name.
524 If the name is a relative path (begins with ./, ../, or a file
525 name), then it is looked up relative to the supplied directory
526 node, or to the top level directory of the FS (supplied at
527 construction time) if no directory is supplied.
533 if isinstance(name, Entry):
534 return self.__checkClass(name, klass)
536 if directory and not isinstance(directory, Dir):
537 directory = self.Dir(directory)
538 name, directory = self.__transformPath(name, directory)
539 return self.__doLookup(klass, name, directory, create)
541 def File(self, name, directory = None, create = 1):
542 """Lookup or create a File node with the specified name. If
543 the name is a relative path (begins with ./, ../, or a file name),
544 then it is looked up relative to the supplied directory node,
545 or to the top level directory of the FS (supplied at construction
546 time) if no directory is supplied.
548 This method will raise TypeError if a directory is found at the
552 return self.Entry(name, directory, create, File)
554 def Dir(self, name, directory = None, create = 1):
555 """Lookup or create a Dir node with the specified name. If
556 the name is a relative path (begins with ./, ../, or a file name),
557 then it is looked up relative to the supplied directory node,
558 or to the top level directory of the FS (supplied at construction
559 time) if no directory is supplied.
561 This method will raise TypeError if a normal file is found at the
565 return self.Entry(name, directory, create, Dir)
567 def BuildDir(self, build_dir, src_dir, duplicate=1):
568 """Link the supplied build directory to the source directory
569 for purposes of building files."""
571 self.__setTopLevelDir()
572 if not isinstance(src_dir, SCons.Node.Node):
573 src_dir = self.Dir(src_dir)
574 if not isinstance(build_dir, SCons.Node.Node):
575 build_dir = self.Dir(build_dir)
576 if not src_dir.is_under(self.Top):
577 raise SCons.Errors.UserError, "Source directory must be under top of build tree."
578 if src_dir.is_under(build_dir):
579 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
580 build_dir.link(src_dir, duplicate)
582 def Repository(self, *dirs):
583 """Specify Repository directories to search."""
585 if not isinstance(d, SCons.Node.Node):
587 self.__setTopLevelDir()
588 self.Top.addRepository(d)
590 def Rsearch(self, path, clazz=_classEntry, cwd=None):
591 """Search for something in a Repository. Returns the first
592 one found in the list, or None if there isn't one."""
593 if isinstance(path, SCons.Node.Node):
596 name, d = self.__transformPath(path, cwd)
597 n = self.__doLookup(clazz, name, d)
602 # Search repositories of all directories that this file is under.
604 for rep in d.getRepositories():
606 rnode = self.__doLookup(clazz, name, rep)
607 # Only find the node if it exists and it is not
608 # a derived file. If for some reason, we are
609 # explicitly building a file IN a Repository, we
610 # don't want it to show up in the build tree.
611 # This is usually the case with BuildDir().
612 # We only want to find pre-existing files.
613 if rnode.exists() and \
614 (isinstance(rnode, Dir) or not rnode.has_builder()):
617 pass # Wrong type of node.
618 # Prepend directory name
619 name = d.name + os.sep + name
620 # Go up one directory
625 def Rsearchall(self, pathlist, must_exist=1, clazz=_classEntry, cwd=None):
626 """Search for a list of somethings in the Repository list."""
628 if SCons.Util.is_String(pathlist):
629 pathlist = string.split(pathlist, os.pathsep)
630 if not SCons.Util.is_List(pathlist):
631 pathlist = [pathlist]
632 for path in pathlist:
633 if isinstance(path, SCons.Node.Node):
636 name, d = self.__transformPath(path, cwd)
637 n = self.__doLookup(clazz, name, d)
638 if not must_exist or n.exists():
640 if isinstance(n, Dir):
641 # If this node is a directory, then any repositories
642 # attached to this node can be repository paths.
643 ret.extend(filter(lambda x, me=must_exist, clazz=clazz: isinstance(x, clazz) and (not me or x.exists()),
644 n.getRepositories()))
648 # Search repositories of all directories that this file is under.
650 for rep in d.getRepositories():
652 rnode = self.__doLookup(clazz, name, rep)
653 # Only find the node if it exists (or must_exist is zero)
654 # and it is not a derived file. If for some reason, we
655 # are explicitly building a file IN a Repository, we don't
656 # want it to show up in the build tree. This is usually the
657 # case with BuildDir(). We only want to find pre-existing files.
658 if (not must_exist or rnode.exists()) and \
659 (not rnode.has_builder() or isinstance(rnode, Dir)):
662 pass # Wrong type of node.
663 # Prepend directory name
664 name = d.name + os.sep + name
665 # Go up one directory
669 def CacheDir(self, path):
670 self.CachePath = path
673 # Annotate with the creator
679 """A class for directories in a file system.
682 def __init__(self, name, directory, fs):
683 Entry.__init__(self, name, directory, fs)
687 """Turn a file system node (either a freshly initialized
688 directory object or a separate Entry object) into a
689 proper directory object.
691 Modify our paths to add the trailing slash that indicates
692 a directory. Set up this directory's entries and hook it
693 into the file system tree. Specify that directories (this
694 node) don't use signatures for currency calculation."""
696 self.path_ = self.path + os.sep
697 self.abspath_ = self.abspath + os.sep
698 self.repositories = []
702 self.entries['.'] = self
703 self.entries['..'] = self.dir
706 self._sconsign = None
708 def __clearRepositoryCache(self, duplicate=None):
709 """Called when we change the repository(ies) for a directory.
710 This clears any cached information that is invalidated by changing
713 for node in self.entries.values():
715 if node != self and isinstance(node, Dir):
716 node.__clearRepositoryCache(duplicate)
720 except AttributeError:
724 except AttributeError:
728 except AttributeError:
732 except AttributeError:
736 except AttributeError:
738 if duplicate != None:
739 node.duplicate=duplicate
741 def __resetDuplicate(self, node):
743 node.duplicate = node.get_dir().duplicate
746 """Create a directory node named 'name' relative to this directory."""
747 return self.fs.Dir(name, self)
749 def File(self, name):
750 """Create file node named 'name' relatove to this directory."""
751 return self.fs.File(name, self)
753 def link(self, srcdir, duplicate):
754 """Set this directory as the build directory for the
755 supplied source directory."""
757 self.duplicate = duplicate
758 self.__clearRepositoryCache(duplicate)
760 def getRepositories(self):
761 """Returns a list of repositories for this directory."""
762 if self.srcdir and not self.duplicate:
765 except AttributeError:
766 self._srcreps = self.fs.Rsearchall(self.srcdir.path,
772 return self.repositories
774 def addRepository(self, dir):
775 if not dir in self.repositories and dir != self:
776 self.repositories.append(dir)
777 self.__clearRepositoryCache()
780 return self.entries['..']
783 if not self.entries['..']:
786 return self.entries['..'].root()
788 def all_children(self, scan):
789 keys = filter(lambda k: k != '.' and k != '..', self.entries.keys())
790 kids = map(lambda x, s=self: s.entries[x], keys)
792 if one.abspath < two.abspath:
794 if one.abspath > two.abspath:
798 return kids + SCons.Node.Node.all_children(self, 0)
800 def get_actions(self):
801 """A null "builder" for directories."""
805 """A null "builder" for directories."""
808 def calc_signature(self, calc):
809 """A directory has no signature."""
812 def set_bsig(self, bsig):
813 """A directory has no signature."""
816 def set_csig(self, csig):
817 """A directory has no signature."""
820 def get_contents(self):
821 """Return a fixed "contents" value of a directory."""
824 def current(self, calc):
825 """If all of our children were up-to-date, then this
826 directory was up-to-date, too."""
828 for kid in self.children(None):
830 if s and (not state or s > state):
833 if state == 0 or state == SCons.Node.up_to_date:
839 """Return the .sconsign file info for this directory,
840 creating it first if necessary."""
841 if not self._sconsign:
843 self._sconsign = SCons.Sig.SConsignFile(self)
844 return self._sconsign
847 """Dir has a special need for srcnode()...if we
848 have a srcdir attribute set, then that *is* our srcnode."""
851 return Entry.srcnode(self)
861 """A class for files in a file system.
863 def __init__(self, name, directory, fs):
864 Entry.__init__(self, name, directory, fs)
869 """Create a directory node named 'name' relative to
870 the SConscript directory of this file."""
871 return self.fs.Dir(name, self.cwd)
873 def File(self, name):
874 """Create a file node named 'name' relative to
875 the SConscript directory of this file."""
876 return self.fs.File(name, self.cwd)
878 def RDirs(self, pathlist):
879 """Search for a list of directories in the Repository list."""
880 return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
883 def generate_build_env(self):
884 env = SCons.Node.Node.generate_build_env(self)
886 return env.Override({'Dir' : self.Dir,
888 'RDirs' : self.RDirs})
891 """Turn a file system node into a File object."""
893 self.scanner_paths = {}
894 self.found_includes = {}
895 if not hasattr(self, '_local'):
899 return self.dir.root()
901 def get_contents(self):
902 if not self.rexists():
904 return open(self.rfile().abspath, "rb").read()
906 def get_timestamp(self):
908 return os.path.getmtime(self.rfile().abspath)
912 def calc_signature(self, calc, cache=None):
914 Select and calculate the appropriate build signature for a File.
917 calc - the signature calculation module
918 cache - alternate node to use for the signature cache
919 returns - the signature
922 if self.has_builder():
923 if SCons.Sig.build_signature:
924 return calc.bsig(self.rfile(), self)
926 return calc.csig(self.rfile(), self)
927 elif not self.rexists():
930 return calc.csig(self.rfile(), self)
932 def store_csig(self):
933 self.dir.sconsign().set_csig(self.name, self.get_csig())
935 def store_bsig(self):
936 self.dir.sconsign().set_bsig(self.name, self.get_bsig())
938 def store_implicit(self):
939 self.dir.sconsign().set_implicit(self.name, self.implicit)
941 def store_timestamp(self):
942 self.dir.sconsign().set_timestamp(self.name, self.get_timestamp())
944 def get_prevsiginfo(self):
945 """Fetch the previous signature information from the
947 return self.dir.sconsign().get(self.name)
949 def get_stored_implicit(self):
950 return self.dir.sconsign().get_implicit(self.name)
952 def get_found_includes(self, env, scanner, target):
953 """Return the included implicit dependencies in this file.
954 Cache results so we only scan the file once regardless of
955 how many times this information is requested."""
960 path = target.scanner_paths[scanner]
961 except AttributeError:
962 # The target had no scanner_paths attribute, which means
963 # it's an Alias or some other node that's not actually a
964 # file. In that case, back off and use the path for this
967 path = self.scanner_paths[scanner]
969 path = scanner.path(env, self.cwd)
970 self.scanner_paths[scanner] = path
972 path = scanner.path(env, target.cwd)
973 target.scanner_paths[scanner] = path
976 includes = self.found_includes[path]
978 includes = scanner(self, env, path)
979 self.found_includes[path] = includes
983 def scanner_key(self):
984 return os.path.splitext(self.name)[1]
986 def _createDir(self):
987 # ensure that the directories for this node are
995 listDirs.append(parent)
997 if isinstance(p, ParentOfRoot):
998 raise SCons.Errors.StopError, parent.path
1001 for dirnode in listDirs:
1003 Mkdir(dirnode, None, None)
1004 # The Mkdir() action may or may not have actually
1005 # created the directory, depending on whether the -n
1006 # option was used or not. Delete the _exists and
1007 # _rexists attributes so they can be reevaluated.
1008 if hasattr(dirnode, '_exists'):
1009 delattr(dirnode, '_exists')
1010 if hasattr(dirnode, '_rexists'):
1011 delattr(dirnode, '_rexists')
1016 """Actually build the file.
1018 This overrides the base class build() method to check for the
1019 existence of derived files in a CacheDir before going ahead and
1022 This method is called from multiple threads in a parallel build,
1023 so only do thread safe stuff here. Do thread unsafe stuff in
1026 if not self.has_builder():
1028 if self.fs.CachePath:
1029 if self.fs.cache_show:
1030 if CacheRetrieveSilent(self, None, None) == 0:
1031 def do_print(action, targets, sources, env, self=self):
1032 al = action.strfunction(targets, self.sources, env)
1033 if not SCons.Util.is_List(al):
1037 self._for_each_action(do_print)
1039 elif CacheRetrieve(self, None, None) == 0:
1041 SCons.Node.Node.build(self)
1044 """Called just after this node is sucessfully built."""
1045 # Push this file out to cache before the superclass Node.built()
1046 # method has a chance to clear the build signature, which it
1047 # will do if this file has a source scanner.
1048 if self.fs.CachePath and os.path.exists(self.path):
1049 CachePush(self, None, None)
1050 SCons.Node.Node.built(self)
1051 self.found_includes = {}
1052 if hasattr(self, '_exists'):
1053 delattr(self, '_exists')
1054 if hasattr(self, '_rexists'):
1055 delattr(self, '_rexists')
1058 if self.fs.CachePath and self.fs.cache_force and os.path.exists(self.path):
1059 CachePush(self, None, None)
1061 def has_builder(self, fetch = 1):
1062 """Return whether this Node has a builder or not.
1064 If this Node doesn't have an explicit builder, this is where we
1065 figure out, on the fly, if there's a source code builder for it.
1069 except AttributeError:
1070 if fetch and not os.path.exists(self.path):
1071 b = self.src_builder()
1075 return not b is None
1078 """Prepare for this file to be created."""
1081 return not node.has_builder() and not node.linked and not node.rexists()
1082 missing_sources = filter(missing, self.children())
1084 desc = "No Builder for target `%s', needed by `%s'." % (missing_sources[0], self)
1085 raise SCons.Errors.StopError, desc
1088 if self.has_builder() and not self.precious:
1090 Unlink(self, None, None)
1092 raise SCons.Errors.BuildError(node = self,
1093 errstr = e.strerror)
1094 if hasattr(self, '_exists'):
1095 delattr(self, '_exists')
1099 except SCons.Errors.StopError, drive:
1100 desc = "No drive `%s' for target `%s'." % (drive, self)
1101 raise SCons.Errors.StopError, desc
1104 """Remove this file."""
1105 if _existsp(self.path):
1106 os.unlink(self.path)
1111 # Duplicate from source path if we are set up to do this.
1112 if self.duplicate and not self.has_builder() and not self.linked:
1113 src=self.srcnode().rfile()
1114 if src.exists() and src.abspath != self.abspath:
1117 Unlink(self, None, None)
1121 Link(self, src, None)
1123 desc = "Cannot duplicate `%s' in `%s': %s." % (src, self.dir, e.strerror)
1124 raise SCons.Errors.StopError, desc
1126 # The Link() action may or may not have actually
1127 # created the file, depending on whether the -n
1128 # option was used or not. Delete the _exists and
1129 # _rexists attributes so they can be reevaluated.
1130 if hasattr(self, '_exists'):
1131 delattr(self, '_exists')
1132 if hasattr(self, '_rexists'):
1133 delattr(self, '_rexists')
1134 return Entry.exists(self)
1136 def current(self, calc):
1137 bsig = calc.bsig(self)
1138 if not self.exists():
1139 # The file doesn't exist locally...
1142 # ...but there is one in a Repository...
1143 if calc.current(r, bsig):
1144 # ...and it's even up-to-date...
1146 # ...and they'd like a local copy.
1147 LocalCopy(self, r, None)
1154 return calc.current(self, bsig)
1157 if not hasattr(self, '_rfile'):
1159 if not self.exists():
1160 n = self.fs.Rsearch(self.path, clazz=File,
1167 return str(self.rfile())
1169 def cachepath(self):
1170 if self.fs.CachePath:
1171 bsig = self.get_bsig()
1173 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1175 subdir = string.upper(bsig[0])
1176 dir = os.path.join(self.fs.CachePath, subdir)
1177 return dir, os.path.join(dir, bsig)
1183 def find_file(filename, paths, node_factory = default_fs.File):
1185 find_file(str, [Dir()]) -> [nodes]
1187 filename - a filename to find
1188 paths - a list of directory path *nodes* to search in
1190 returns - the node created from the found file.
1192 Find a node corresponding to either a derived file or a file
1193 that exists already.
1195 Only the first file found is returned, and none is returned
1196 if no file is found.
1201 node = node_factory(filename, dir)
1202 # Return true of the node exists or is a derived node.
1203 if node.has_builder() or \
1204 (isinstance(node, SCons.Node.FS.Entry) and node.exists()):
1208 # If we find a directory instead of a file, we don't care
1213 def find_files(filenames, paths, node_factory = default_fs.File):
1215 find_files([str], [Dir()]) -> [nodes]
1217 filenames - a list of filenames to find
1218 paths - a list of directory path *nodes* to search in
1220 returns - the nodes created from the found files.
1222 Finds nodes corresponding to either derived files or files
1225 Only the first file found is returned for each filename,
1226 and any files that aren't found are ignored.
1228 nodes = map(lambda x, paths=paths, node_factory=node_factory:
1229 find_file(x, paths, node_factory),
1231 return filter(lambda x: x != None, nodes)