5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
8 This holds a "default_fs" variable that should be initialized with an FS
9 that can be used by scripts or modules looking for the canonical default.
16 # Permission is hereby granted, free of charge, to any person obtaining
17 # a copy of this software and associated documentation files (the
18 # "Software"), to deal in the Software without restriction, including
19 # without limitation the rights to use, copy, modify, merge, publish,
20 # distribute, sublicense, and/or sell copies of the Software, and to
21 # permit persons to whom the Software is furnished to do so, subject to
22 # the following conditions:
24 # The above copyright notice and this permission notice shall be included
25 # in all copies or substantial portions of the Software.
27 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
28 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
29 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
48 from SCons.Debug import logInstanceCreation
56 # We stringify these file system Nodes a lot. Turning a file system Node
57 # into a string is non-trivial, because the final string representation
58 # can depend on a lot of factors: whether it's a derived target or not,
59 # whether it's linked to a repository or source directory, and whether
60 # there's duplication going on. The normal technique for optimizing
61 # calculations like this is to memoize (cache) the string value, so you
62 # only have to do the calculation once.
64 # A number of the above factors, however, can be set after we've already
65 # been asked to return a string for a Node, because a Repository() or
66 # BuildDir() call or the like may not occur until later in SConscript
67 # files. So this variable controls whether we bother trying to save
68 # string values for Nodes. The wrapper interface can set this whenever
69 # they're done mucking with Repository and BuildDir and the other stuff,
70 # to let this module know it can start returning saved string values
75 def save_strings(val):
80 # SCons.Action objects for interacting with the outside world.
82 # The Node.FS methods in this module should use these actions to
83 # create and/or remove files and directories; they should *not* use
84 # os.{link,symlink,unlink,mkdir}(), etc., directly.
86 # Using these SCons.Action objects ensures that descriptions of these
87 # external activities are properly displayed, that the displays are
88 # suppressed when the -s (silent) option is used, and (most importantly)
89 # the actions are disabled when the the -n option is used, in which case
90 # there should be *no* changes to the external file system(s)...
93 def _copy_func(src, dest):
94 shutil.copy2(src, dest)
96 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
98 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
99 'hard-copy', 'soft-copy', 'copy']
101 Link_Funcs = [] # contains the callables of the specified duplication style
103 def set_duplicate(duplicate):
104 # Fill in the Link_Funcs list according to the argument
105 # (discarding those not available on the platform).
107 # Set up the dictionary that maps the argument names to the
108 # underlying implementations. We do this inside this function,
109 # not in the top-level module code, so that we can remap os.link
110 # and os.symlink for testing purposes.
112 _hardlink_func = os.link
113 except AttributeError:
114 _hardlink_func = None
117 _softlink_func = os.symlink
118 except AttributeError:
119 _softlink_func = None
122 'hard' : _hardlink_func,
123 'soft' : _softlink_func,
127 if not duplicate in Valid_Duplicates:
128 raise SCons.Errors.InternalError, ("The argument of set_duplicate "
129 "should be in Valid_Duplicates")
132 for func in string.split(duplicate,'-'):
134 Link_Funcs.append(link_dict[func])
136 def LinkFunc(target, source, env):
137 # Relative paths cause problems with symbolic links, so
138 # we use absolute paths, which may be a problem for people
139 # who want to move their soft-linked src-trees around. Those
140 # people should use the 'hard-copy' mode, softlinks cannot be
141 # used for that; at least I have no idea how ...
142 src = source[0].abspath
143 dest = target[0].abspath
144 dir, file = os.path.split(dest)
145 if dir and not target[0].fs.isdir(dir):
148 # Set a default order of link functions.
149 set_duplicate('hard-soft-copy')
150 # Now link the files with the previously specified order.
151 for func in Link_Funcs:
156 if func == Link_Funcs[-1]:
157 # exception of the last link method (copy) are fatal
163 Link = SCons.Action.Action(LinkFunc, None)
164 def LocalString(target, source, env):
165 return 'Local copy of %s from %s' % (target[0], source[0])
167 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
169 def UnlinkFunc(target, source, env):
171 t.fs.unlink(t.abspath)
174 Unlink = SCons.Action.Action(UnlinkFunc, None)
176 def MkdirFunc(target, source, env):
179 t.fs.mkdir(t.abspath)
182 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
186 def get_MkdirBuilder():
188 if MkdirBuilder is None:
190 # "env" will get filled in by Executor.get_build_env()
191 # calling SCons.Defaults.DefaultEnvironment() when necessary.
192 MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
196 name = "MkdirBuilder")
199 def CacheRetrieveFunc(target, source, env):
202 cachedir, cachefile = t.cachepath()
203 if fs.exists(cachefile):
204 if SCons.Action.execute_actions:
205 fs.copy2(cachefile, t.path)
206 st = fs.stat(cachefile)
207 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
211 def CacheRetrieveString(target, source, env):
213 cachedir, cachefile = t.cachepath()
214 if t.fs.exists(cachefile):
215 return "Retrieved `%s' from cache" % t.path
218 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
220 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
222 def CachePushFunc(target, source, env):
225 cachedir, cachefile = t.cachepath()
226 if fs.exists(cachefile):
227 # Don't bother copying it if it's already there.
230 if not fs.isdir(cachedir):
231 fs.makedirs(cachedir)
233 tempfile = cachefile+'.tmp'
235 fs.copy2(t.path, tempfile)
236 fs.rename(tempfile, cachefile)
238 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
240 # It's possible someone else tried writing the file at the same
241 # time we did. Print a warning but don't stop the build, since
242 # it doesn't affect the correctness of the build.
243 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
244 "Unable to copy %s to cache. Cache file is %s"
245 % (str(target), cachefile))
248 CachePush = SCons.Action.Action(CachePushFunc, None)
255 DefaultSCCSBuilder = None
256 DefaultRCSBuilder = None
258 def get_DefaultSCCSBuilder():
259 global DefaultSCCSBuilder
260 if DefaultSCCSBuilder is None:
262 # "env" will get filled in by Executor.get_build_env()
263 # calling SCons.Defaults.DefaultEnvironment() when necessary.
264 act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
265 DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
267 name = "DefaultSCCSBuilder")
268 return DefaultSCCSBuilder
270 def get_DefaultRCSBuilder():
271 global DefaultRCSBuilder
272 if DefaultRCSBuilder is None:
274 # "env" will get filled in by Executor.get_build_env()
275 # calling SCons.Defaults.DefaultEnvironment() when necessary.
276 act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
277 DefaultRCSBuilder = SCons.Builder.Builder(action = act,
279 name = "DefaultRCSBuilder")
280 return DefaultRCSBuilder
282 # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
283 _is_cygwin = sys.platform == "cygwin"
284 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
289 return string.upper(x)
291 class EntryProxy(SCons.Util.Proxy):
292 def __get_abspath(self):
294 return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
295 entry.name + "_abspath")
297 def __get_filebase(self):
298 name = self.get().name
299 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
302 def __get_suffix(self):
303 name = self.get().name
304 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
307 def __get_file(self):
308 name = self.get().name
309 return SCons.Util.SpecialAttrWrapper(name, name + "_file")
311 def __get_base_path(self):
312 """Return the file's directory and file name, with the
315 return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
316 entry.name + "_base")
318 def __get_posix_path(self):
319 """Return the path with / as the path separator,
320 regardless of platform."""
325 r = string.replace(entry.get_path(), os.sep, '/')
326 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_posix")
328 def __get_win32_path(self):
329 """Return the path with \ as the path separator,
330 regardless of platform."""
335 r = string.replace(entry.get_path(), os.sep, '\\')
336 return SCons.Util.SpecialAttrWrapper(r, entry.name + "_win32")
338 def __get_srcnode(self):
339 return EntryProxy(self.get().srcnode())
341 def __get_srcdir(self):
342 """Returns the directory containing the source node linked to this
343 node via BuildDir(), or the directory of this node if not linked."""
344 return EntryProxy(self.get().srcnode().dir)
346 def __get_rsrcnode(self):
347 return EntryProxy(self.get().srcnode().rfile())
349 def __get_rsrcdir(self):
350 """Returns the directory containing the source node linked to this
351 node via BuildDir(), or the directory of this node if not linked."""
352 return EntryProxy(self.get().srcnode().rfile().dir)
355 return EntryProxy(self.get().dir)
357 dictSpecialAttrs = { "base" : __get_base_path,
358 "posix" : __get_posix_path,
359 "win32" : __get_win32_path,
360 "srcpath" : __get_srcnode,
361 "srcdir" : __get_srcdir,
363 "abspath" : __get_abspath,
364 "filebase" : __get_filebase,
365 "suffix" : __get_suffix,
367 "rsrcpath" : __get_rsrcnode,
368 "rsrcdir" : __get_rsrcdir,
371 def __getattr__(self, name):
372 # This is how we implement the "special" attributes
373 # such as base, posix, srcdir, etc.
375 return self.dictSpecialAttrs[name](self)
378 attr = SCons.Util.Proxy.__getattr__(self, name)
379 except AttributeError:
381 classname = string.split(str(entry.__class__), '.')[-1]
382 if classname[-2:] == "'>":
383 # new-style classes report their name as:
384 # "<class 'something'>"
385 # instead of the classic classes:
387 classname = classname[:-2]
388 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
391 class Base(SCons.Node.Node):
392 """A generic class for file system entries. This class is for
393 when we don't know yet whether the entry being looked up is a file
394 or a directory. Instances of this class can morph into either
395 Dir or File objects by a later, more precise lookup.
397 Note: this class does not define __cmp__ and __hash__ for
398 efficiency reasons. SCons does a lot of comparing of
399 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
400 as fast as possible, which means we want to use Python's built-in
401 object identity comparisons.
404 def __init__(self, name, directory, fs):
405 """Initialize a generic Node.FS.Base object.
407 Call the superclass initialization, take care of setting up
408 our relative and absolute paths, identify our parent
409 directory, and indicate that this node should use
411 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
412 SCons.Node.Node.__init__(self)
417 assert directory, "A directory must be provided"
419 self.abspath = directory.entry_abspath(name)
420 if directory.path == '.':
423 self.path = directory.entry_path(name)
424 if directory.tpath == '.':
427 self.tpath = directory.entry_tpath(name)
428 self.path_elements = directory.path_elements + [self]
431 self.cwd = None # will hold the SConscript directory for target nodes
432 self.duplicate = directory.duplicate
435 """Completely clear a Node.FS.Base object of all its cached
436 state (so that it can be re-evaluated by interfaces that do
437 continuous integration builds).
440 SCons.Node.Node.clear(self)
445 def get_suffix(self):
447 return SCons.Util.splitext(self.name)[1]
453 """A Node.FS.Base object's string representation is its path
457 return self._save_str()
458 return self._get_str()
462 return self._get_str()
465 if self.duplicate or self.is_derived():
466 return self.get_path()
467 return self.srcnode().get_path()
473 try: return self.fs.stat(self.abspath)
474 except os.error: return None
478 return not self.stat() is None
482 return self.rfile().exists()
485 return self.stat()[stat.ST_MTIME]
489 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
493 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
495 if hasattr(os, 'symlink'):
497 try: st = self.fs.lstat(self.abspath)
498 except os.error: return 0
499 return stat.S_ISLNK(st[stat.ST_MODE])
502 return 0 # no symlinks
504 def is_under(self, dir):
508 return self.dir.is_under(dir)
514 """If this node is in a build path, return the node
515 corresponding to its source file. Otherwise, return
522 srcnode = self.fs.Entry(name, dir.srcdir,
523 klass=self.__class__)
525 name = dir.name + os.sep + name
529 def get_path(self, dir=None):
530 """Return path relative to the current working directory of the
531 Node.FS.Base object that owns us."""
533 dir = self.fs.getcwd()
536 path_elems = self.path_elements
537 try: i = path_elems.index(dir)
538 except ValueError: pass
539 else: path_elems = path_elems[i+1:]
540 path_elems = map(lambda n: n.name, path_elems)
541 return string.join(path_elems, os.sep)
543 def set_src_builder(self, builder):
544 """Set the source code builder for this node."""
545 self.sbuilder = builder
546 if not self.has_builder():
547 self.builder_set(builder)
549 def src_builder(self):
550 """Fetch the source code builder for this node.
552 If there isn't one, we cache the source code builder specified
553 for the directory (which in turn will cache the value from its
554 parent directory, and so on up to the file system root).
558 except AttributeError:
559 scb = self.dir.src_builder()
563 def get_abspath(self):
564 """Get the absolute path of the file."""
567 def for_signature(self):
568 # Return just our name. Even an absolute path would not work,
569 # because that can change thanks to symlinks or remapped network
573 def get_subst_proxy(self):
576 except AttributeError:
577 ret = EntryProxy(self)
582 """This is the class for generic Node.FS entries--that is, things
583 that could be a File or a Dir, but we're just not sure yet.
584 Consequently, the methods in this class really exist just to
585 transform their associated object into the right class when the
586 time comes, and then call the same-named method in the transformed
589 def disambiguate(self):
594 self.__class__ = File
600 """We're a generic Entry, but the caller is actually looking for
601 a File at this point, so morph into one."""
602 self.__class__ = File
605 return File.rfile(self)
607 def get_found_includes(self, env, scanner, path):
608 """If we're looking for included files, it's because this Entry
609 is really supposed to be a File itself."""
610 return self.disambiguate().get_found_includes(env, scanner, path)
612 def scanner_key(self):
613 return self.get_suffix()
615 def get_contents(self):
616 """Fetch the contents of the entry.
618 Since this should return the real contents from the file
619 system, we check to see into what sort of subclass we should
622 self.__class__ = File
624 return self.get_contents()
628 return self.get_contents()
630 return '' # avoid errors for dangling symlinks
633 def rel_path(self, other):
634 return self.disambiguate().rel_path(other)
637 """Return if the Entry exists. Check the file system to see
638 what we should turn into first. Assume a file if there's no
640 return self.disambiguate().exists()
642 def calc_signature(self, calc=None):
643 """Return the Entry's calculated signature. Check the file
644 system to see what we should turn into first. Assume a file if
645 there's no directory."""
646 return self.disambiguate().calc_signature(calc)
648 def must_be_a_Dir(self):
649 """Called to make sure a Node is a Dir. Since we're an
650 Entry, we can morph into one."""
655 # This is for later so we can differentiate between Entry the class and Entry
656 # the method of the FS class.
662 if SCons.Memoize.use_memoizer:
663 __metaclass__ = SCons.Memoize.Memoized_Metaclass
665 # This class implements an abstraction layer for operations involving
666 # a local file system. Essentially, this wraps any function in
667 # the os, os.path or shutil modules that we use to actually go do
668 # anything with or to the local file system.
670 # Note that there's a very good chance we'll refactor this part of
671 # the architecture in some way as we really implement the interface(s)
672 # for remote file system Nodes. For example, the right architecture
673 # might be to have this be a subclass instead of a base class.
674 # Nevertheless, we're using this as a first step in that direction.
676 # We're not using chdir() yet because the calling subclass method
677 # needs to use os.chdir() directly to avoid recursion. Will we
678 # really need this one?
679 #def chdir(self, path):
680 # return os.chdir(path)
681 def chmod(self, path, mode):
682 return os.chmod(path, mode)
683 def copy2(self, src, dst):
684 return shutil.copy2(src, dst)
685 def exists(self, path):
686 return os.path.exists(path)
687 def getmtime(self, path):
688 return os.path.getmtime(path)
689 def isdir(self, path):
690 return os.path.isdir(path)
691 def isfile(self, path):
692 return os.path.isfile(path)
693 def link(self, src, dst):
694 return os.link(src, dst)
695 def lstat(self, path):
696 return os.lstat(path)
697 def listdir(self, path):
698 return os.listdir(path)
699 def makedirs(self, path):
700 return os.makedirs(path)
701 def mkdir(self, path):
702 return os.mkdir(path)
703 def rename(self, old, new):
704 return os.rename(old, new)
705 def stat(self, path):
707 def symlink(self, src, dst):
708 return os.symlink(src, dst)
709 def open(self, path):
711 def unlink(self, path):
712 return os.unlink(path)
714 if hasattr(os, 'symlink'):
715 def islink(self, path):
716 return os.path.islink(path)
718 def islink(self, path):
719 return 0 # no symlinks
721 if SCons.Memoize.use_old_memoization():
723 class LocalFS(SCons.Memoize.Memoizer, _FSBase):
724 def __init__(self, *args, **kw):
725 apply(_FSBase.__init__, (self,)+args, kw)
726 SCons.Memoize.Memoizer.__init__(self)
730 # # Skeleton for the obvious methods we might need from the
731 # # abstraction layer for a remote filesystem.
732 # def upload(self, local_src, remote_dst):
734 # def download(self, remote_src, local_dst):
740 def __init__(self, path = None):
741 """Initialize the Node.FS subsystem.
743 The supplied path is the top of the source tree, where we
744 expect to find the top-level build file. If no path is
745 supplied, the current directory is the default.
747 The path argument must be a valid absolute path.
749 if __debug__: logInstanceCreation(self, 'Node.FS')
751 self.SConstruct_dir = None
752 self.CachePath = None
753 self.cache_force = None
754 self.cache_show = None
757 self.pathTop = os.getcwd()
761 self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
766 def clear_cache(self):
770 def set_SConstruct_dir(self, dir):
771 self.SConstruct_dir = dir
776 def __checkClass(self, node, klass):
777 if isinstance(node, klass) or klass == Entry:
779 if node.__class__ == Entry:
780 node.__class__ = klass
783 raise TypeError, "Tried to lookup %s '%s' as a %s." % \
784 (node.__class__.__name__, node.path, klass.__name__)
786 def _doLookup(self, fsclass, name, directory = None, create = 1):
787 """This method differs from the File and Dir factory methods in
788 one important way: the meaning of the directory parameter.
789 In this method, if directory is None or not supplied, the supplied
790 name is expected to be an absolute path. If you try to look up a
791 relative path with directory=None, then an AssertionError will be
796 # This is a stupid hack to compensate for the fact that
797 # the POSIX and Win32 versions of os.path.normpath() behave
798 # differently in older versions of Python. In particular,
800 # os.path.normpath('./') == '.'
802 # os.path.normpath('./') == ''
803 # os.path.normpath('.\\') == ''
805 # This is a definite bug in the Python library, but we have
808 path_orig = string.split(name, os.sep)
809 path_norm = string.split(_my_normcase(name), os.sep)
811 first_orig = path_orig.pop(0) # strip first element
812 first_norm = path_norm.pop(0) # strip first element
814 drive, path_first = os.path.splitdrive(first_orig)
816 path_orig = [ path_first, ] + path_orig
817 path_norm = [ _my_normcase(path_first), ] + path_norm
821 directory = self.Root[drive]
824 raise SCons.Errors.UserError
825 directory = RootDir(drive, self)
826 self.Root[drive] = directory
831 last_orig = path_orig.pop() # strip last element
832 last_norm = path_norm.pop() # strip last element
834 # Lookup the directory
835 for orig, norm in map(None, path_orig, path_norm):
837 directory = directory.entries[norm]
840 raise SCons.Errors.UserError
842 # look at the actual filesystem and make sure there isn't
843 # a file already there
844 path = directory.entry_abspath(orig)
845 if self.isfile(path):
847 "File %s found where directory expected." % path
849 d = Dir(orig, directory, self)
850 directory.entries[norm] = d
851 directory.add_wkid(d)
853 except AttributeError:
854 # We tried to look up the entry in either an Entry or
855 # a File. Give whatever it is a chance to do what's
856 # appropriate: morph into a Dir or raise an exception.
857 directory.must_be_a_Dir()
858 directory = directory.entries[norm]
860 directory.must_be_a_Dir()
863 e = directory.entries[last_norm]
866 raise SCons.Errors.UserError
868 # make sure we don't create File nodes when there is actually
869 # a directory at that path on the disk, and vice versa
870 path = directory.entry_abspath(last_orig)
874 "Directory %s found where file expected." % path
876 if self.isfile(path):
878 "File %s found where directory expected." % path
880 result = fsclass(last_orig, directory, self)
881 directory.entries[last_norm] = result
882 directory.add_wkid(result)
884 result = self.__checkClass(e, fsclass)
887 def _transformPath(self, name, directory):
888 """Take care of setting up the correct top-level directory,
889 usually in preparation for a call to doLookup().
891 If the path name is prepended with a '#', then it is unconditionally
892 interpreted as relative to the top-level directory of this FS.
894 If directory is None, and name is a relative path,
895 then the same applies.
897 if name and name[0] == '#':
900 if name and (name[0] == os.sep or name[0] == '/'):
901 # Correct such that '#/foo' is equivalent
904 name = os.path.join('.', os.path.normpath(name))
906 directory = self._cwd
907 return (os.path.normpath(name), directory)
909 def chdir(self, dir, change_os_dir=0):
910 """Change the current working directory for lookups.
911 If change_os_dir is true, we will also change the "real" cwd
919 os.chdir(dir.abspath)
924 def Entry(self, name, directory = None, create = 1, klass=None):
925 """Lookup or create a generic Entry node with the specified name.
926 If the name is a relative path (begins with ./, ../, or a file
927 name), then it is looked up relative to the supplied directory
928 node, or to the top level directory of the FS (supplied at
929 construction time) if no directory is supplied.
935 if isinstance(name, Base):
936 return self.__checkClass(name, klass)
938 if directory and not isinstance(directory, Dir):
939 directory = self.Dir(directory)
940 name, directory = self._transformPath(name, directory)
941 return self._doLookup(klass, name, directory, create)
943 def File(self, name, directory = None, create = 1):
944 """Lookup or create a File node with the specified name. If
945 the name is a relative path (begins with ./, ../, or a file name),
946 then it is looked up relative to the supplied directory node,
947 or to the top level directory of the FS (supplied at construction
948 time) if no directory is supplied.
950 This method will raise TypeError if a directory is found at the
954 return self.Entry(name, directory, create, File)
956 def Dir(self, name, directory = None, create = 1):
957 """Lookup or create a Dir node with the specified name. If
958 the name is a relative path (begins with ./, ../, or a file name),
959 then it is looked up relative to the supplied directory node,
960 or to the top level directory of the FS (supplied at construction
961 time) if no directory is supplied.
963 This method will raise TypeError if a normal file is found at the
967 return self.Entry(name, directory, create, Dir)
969 def BuildDir(self, build_dir, src_dir, duplicate=1):
970 """Link the supplied build directory to the source directory
971 for purposes of building files."""
973 if not isinstance(src_dir, SCons.Node.Node):
974 src_dir = self.Dir(src_dir)
975 if not isinstance(build_dir, SCons.Node.Node):
976 build_dir = self.Dir(build_dir)
977 if src_dir.is_under(build_dir):
978 raise SCons.Errors.UserError, "Source directory cannot be under build directory."
980 if build_dir.srcdir == src_dir:
981 return # We already did this.
982 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(build_dir, build_dir.srcdir)
983 build_dir.link(src_dir, duplicate)
985 def Repository(self, *dirs):
986 """Specify Repository directories to search."""
988 if not isinstance(d, SCons.Node.Node):
990 self.Top.addRepository(d)
992 def Rfindalldirs(self, pathlist, cwd):
994 if SCons.Util.is_String(pathlist):
995 pathlist = string.split(pathlist, os.pathsep)
996 if not SCons.Util.is_List(pathlist):
997 pathlist = [pathlist]
999 for path in filter(None, pathlist):
1000 if isinstance(path, SCons.Node.Node):
1003 path, dir = self._transformPath(path, cwd)
1005 result.extend(dir.get_all_rdirs())
1008 def CacheDir(self, path):
1009 self.CachePath = path
1011 def build_dir_target_climb(self, orig, dir, tail):
1012 """Create targets in corresponding build directories
1014 Climb the directory tree, and look up path names
1015 relative to any linked build directories we find.
1020 fmt = "building associated BuildDir targets: %s"
1023 for bd in dir.build_dirs:
1024 if start_dir.is_under(bd):
1025 # If already in the build-dir location, don't reflect
1026 return [orig], fmt % str(orig)
1027 p = apply(os.path.join, [bd.path] + tail)
1028 targets.append(self.Entry(p))
1029 tail = [dir.name] + tail
1032 message = fmt % string.join(map(str, targets))
1033 return targets, message
1036 """A class for directories in a file system.
1039 def __init__(self, name, directory, fs):
1040 if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1041 Base.__init__(self, name, directory, fs)
1045 """Turn a file system Node (either a freshly initialized directory
1046 object or a separate Entry object) into a proper directory object.
1048 Set up this directory's entries and hook it into the file
1049 system tree. Specify that directories (this Node) don't use
1050 signatures for calculating whether they're current.
1053 self.repositories = []
1057 self.entries['.'] = self
1058 self.entries['..'] = self.dir
1061 self._sconsign = None
1062 self.build_dirs = []
1064 # Don't just reset the executor, replace its action list,
1065 # because it might have some pre-or post-actions that need to
1067 self.builder = get_MkdirBuilder()
1068 self.get_executor().set_action_list(self.builder.action)
1070 def disambiguate(self):
1073 def __clearRepositoryCache(self, duplicate=None):
1074 """Called when we change the repository(ies) for a directory.
1075 This clears any cached information that is invalidated by changing
1078 for node in self.entries.values():
1079 if node != self.dir:
1080 if node != self and isinstance(node, Dir):
1081 node.__clearRepositoryCache(duplicate)
1086 except AttributeError:
1088 if duplicate != None:
1089 node.duplicate=duplicate
1091 def __resetDuplicate(self, node):
1093 node.duplicate = node.get_dir().duplicate
1095 def Entry(self, name):
1096 """Create an entry node named 'name' relative to this directory."""
1097 return self.fs.Entry(name, self)
1099 def Dir(self, name):
1100 """Create a directory node named 'name' relative to this directory."""
1101 return self.fs.Dir(name, self)
1103 def File(self, name):
1104 """Create a file node named 'name' relative to this directory."""
1105 return self.fs.File(name, self)
1107 def link(self, srcdir, duplicate):
1108 """Set this directory as the build directory for the
1109 supplied source directory."""
1110 self.srcdir = srcdir
1111 self.duplicate = duplicate
1112 self.__clearRepositoryCache(duplicate)
1113 srcdir.build_dirs.append(self)
1115 def getRepositories(self):
1116 """Returns a list of repositories for this directory.
1118 if self.srcdir and not self.duplicate:
1119 return self.srcdir.get_all_rdirs() + self.repositories
1120 return self.repositories
1122 def get_all_rdirs(self):
1128 for rep in dir.getRepositories():
1129 result.append(rep.Dir(fname))
1130 fname = dir.name + os.sep + fname
1134 def addRepository(self, dir):
1135 if dir != self and not dir in self.repositories:
1136 self.repositories.append(dir)
1138 self.__clearRepositoryCache()
1141 return self.entries['..']
1143 def rel_path(self, other):
1144 """Return a path to "other" relative to this directory.
1146 if isinstance(other, Dir):
1152 except AttributeError:
1155 return name and name[0] or '.'
1157 for x, y in map(None, self.path_elements, other.path_elements):
1161 path_elems = ['..']*(len(self.path_elements)-i) \
1162 + map(lambda n: n.name, other.path_elements[i:]) \
1165 return string.join(path_elems, os.sep)
1168 if not self.implicit is None:
1171 self.implicit_dict = {}
1172 self._children_reset()
1174 dont_scan = lambda k: k not in ['.', '..', '.sconsign']
1175 deps = filter(dont_scan, self.entries.keys())
1176 # keys() is going to give back the entries in an internal,
1177 # unsorted order. Sort 'em so the order is deterministic.
1179 entries = map(lambda n, e=self.entries: e[n], deps)
1181 self._add_child(self.implicit, self.implicit_dict, entries)
1183 def get_found_includes(self, env, scanner, path):
1184 """Return the included implicit dependencies in this file.
1185 Cache results so we only scan the file once per path
1186 regardless of how many times this information is requested.
1190 # Clear cached info for this Node. If we already visited this
1191 # directory on our walk down the tree (because we didn't know at
1192 # that point it was being used as the source for another Node)
1193 # then we may have calculated build signature before realizing
1194 # we had to scan the disk. Now that we have to, though, we need
1195 # to invalidate the old calculated signature so that any node
1196 # dependent on our directory structure gets one that includes
1197 # info about everything on disk.
1199 return scanner(self, env, path)
1201 def build(self, **kw):
1202 """A null "builder" for directories."""
1204 if not self.builder is MkdirBuilder:
1205 apply(SCons.Node.Node.build, [self,], kw)
1208 """Create this directory, silently and without worrying about
1209 whether the builder is the default or not."""
1215 listDirs.append(parent)
1218 raise SCons.Errors.StopError, parent.path
1221 for dirnode in listDirs:
1223 # Don't call dirnode.build(), call the base Node method
1224 # directly because we definitely *must* create this
1225 # directory. The dirnode.build() method will suppress
1226 # the build if it's the default builder.
1227 SCons.Node.Node.build(dirnode)
1228 dirnode.get_executor().nullify()
1229 # The build() action may or may not have actually
1230 # created the directory, depending on whether the -n
1231 # option was used or not. Delete the _exists and
1232 # _rexists attributes so they can be reevaluated.
1237 def multiple_side_effect_has_builder(self):
1239 return not self.builder is MkdirBuilder and self.has_builder()
1241 def alter_targets(self):
1242 """Return any corresponding targets in a build directory.
1244 return self.fs.build_dir_target_climb(self, self, [])
1246 def scanner_key(self):
1247 """A directory does not get scanned."""
1250 def get_contents(self):
1251 """Return aggregate contents of all our children."""
1252 contents = cStringIO.StringIO()
1253 for kid in self.children():
1254 contents.write(kid.get_contents())
1255 return contents.getvalue()
1260 def do_duplicate(self, src):
1263 def current(self, calc=None):
1264 """If all of our children were up-to-date, then this
1265 directory was up-to-date, too."""
1266 if not self.builder is MkdirBuilder and not self.exists():
1269 for kid in self.children():
1271 if s and (not state or s > state):
1274 if state == 0 or state == SCons.Node.up_to_date:
1281 if not self.exists():
1282 norm_name = _my_normcase(self.name)
1283 for dir in self.dir.get_all_rdirs():
1284 try: node = dir.entries[norm_name]
1285 except KeyError: node = dir.dir_on_disk(self.name)
1286 if node and node.exists() and \
1287 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1292 """Return the .sconsign file info for this directory,
1293 creating it first if necessary."""
1294 if not self._sconsign:
1295 import SCons.SConsign
1296 self._sconsign = SCons.SConsign.ForDirectory(self)
1297 return self._sconsign
1300 """Dir has a special need for srcnode()...if we
1301 have a srcdir attribute set, then that *is* our srcnode."""
1304 return Base.srcnode(self)
1306 def get_timestamp(self):
1307 """Return the latest timestamp from among our children"""
1309 for kid in self.children():
1310 if kid.get_timestamp() > stamp:
1311 stamp = kid.get_timestamp()
1314 def entry_abspath(self, name):
1315 return self.abspath + os.sep + name
1317 def entry_path(self, name):
1318 return self.path + os.sep + name
1320 def entry_tpath(self, name):
1321 return self.tpath + os.sep + name
1323 def must_be_a_Dir(self):
1324 """Called to make sure a Node is a Dir. Since we're already
1325 one, this is a no-op for us."""
1328 def entry_exists_on_disk(self, name):
1330 return self.fs.exists(self.entry_abspath(name))
1332 def rcs_on_disk(self, name):
1333 rcspath = 'RCS' + os.sep + name+',v'
1334 return self.entry_exists_on_disk(rcspath)
1336 def sccs_on_disk(self, name):
1337 sccspath = 'SCCS' + os.sep + 's.'+name
1338 return self.entry_exists_on_disk(sccspath)
1340 def srcdir_list(self):
1348 d = dir.srcdir.Dir(dirname)
1350 # Shouldn't source from something in the build path:
1351 # build_dir is probably under src_dir, in which case
1352 # we are reflecting.
1355 dirname = dir.name + os.sep + dirname
1360 def srcdir_duplicate(self, name):
1361 for dir in self.srcdir_list():
1362 if dir.entry_exists_on_disk(name):
1363 srcnode = dir.File(name)
1365 node = self.File(name)
1366 node.do_duplicate(srcnode)
1372 def srcdir_find_file(self, filename):
1375 if (isinstance(node, File) or isinstance(node, Entry)) and \
1376 (node.is_derived() or node.is_pseudo_derived() or node.exists()):
1380 norm_name = _my_normcase(filename)
1382 for rdir in self.get_all_rdirs():
1383 try: node = rdir.entries[norm_name]
1384 except KeyError: node = rdir.file_on_disk(filename)
1385 else: node = func(node)
1389 for srcdir in self.srcdir_list():
1390 for rdir in srcdir.get_all_rdirs():
1391 try: node = rdir.entries[norm_name]
1392 except KeyError: node = rdir.file_on_disk(filename)
1393 else: node = func(node)
1395 return File(filename, self, self.fs), srcdir
1399 def dir_on_disk(self, name):
1400 if self.entry_exists_on_disk(name):
1401 try: return self.Dir(name)
1402 except TypeError: pass
1405 def file_on_disk(self, name):
1406 if self.entry_exists_on_disk(name) or \
1407 self.sccs_on_disk(name) or \
1408 self.rcs_on_disk(name):
1409 try: return self.File(name)
1410 except TypeError: pass
1411 return self.srcdir_duplicate(name)
1414 """A class for the root directory of a file system.
1416 This is the same as a Dir class, except that the path separator
1417 ('/' or '\\') is actually part of the name, so we don't need to
1418 add a separator when creating the path names of entries within
1421 def __init__(self, name, fs):
1422 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1423 # We're going to be our own parent directory (".." entry and .dir
1424 # attribute) so we have to set up some values so Base.__init__()
1425 # won't gag won't it calls some of our methods.
1429 self.path_elements = []
1431 Base.__init__(self, name, self, fs)
1433 # Now set our paths to what we really want them to be: the
1434 # initial drive letter (the name) plus the directory separator.
1435 self.abspath = name + os.sep
1436 self.path = name + os.sep
1437 self.tpath = name + os.sep
1443 def entry_abspath(self, name):
1444 return self.abspath + name
1446 def entry_path(self, name):
1447 return self.path + name
1449 def entry_tpath(self, name):
1450 return self.tpath + name
1452 def is_under(self, dir):
1464 def src_builder(self):
1469 def __cmp__(self, other):
1471 return cmp(self.bsig, other.bsig)
1472 except AttributeError:
1476 """A class for files in a file system.
1478 def __init__(self, name, directory, fs):
1479 if __debug__: logInstanceCreation(self, 'Node.FS.File')
1480 Base.__init__(self, name, directory, fs)
1483 def Entry(self, name):
1484 """Create an entry node named 'name' relative to
1485 the SConscript directory of this file."""
1486 return self.fs.Entry(name, self.cwd)
1488 def Dir(self, name):
1489 """Create a directory node named 'name' relative to
1490 the SConscript directory of this file."""
1491 return self.fs.Dir(name, self.cwd)
1493 def File(self, name):
1494 """Create a file node named 'name' relative to
1495 the SConscript directory of this file."""
1496 return self.fs.File(name, self.cwd)
1498 def RDirs(self, pathlist):
1499 """Search for a list of directories in the Repository list."""
1500 return self.fs.Rfindalldirs(pathlist, self.cwd)
1503 """Turn a file system node into a File object. __cache_reset__"""
1504 self.scanner_paths = {}
1505 if not hasattr(self, '_local'):
1508 def disambiguate(self):
1511 def scanner_key(self):
1512 return self.get_suffix()
1514 def get_contents(self):
1515 if not self.rexists():
1517 return open(self.rfile().abspath, "rb").read()
1519 def get_timestamp(self):
1521 return self.rfile().getmtime()
1525 def store_info(self, obj):
1526 # Merge our build information into the already-stored entry.
1527 # This accomodates "chained builds" where a file that's a target
1528 # in one build (SConstruct file) is a source in a different build.
1529 # See test/chained-build.py for the use case.
1530 entry = self.get_stored_info()
1531 for key, val in obj.__dict__.items():
1532 entry.__dict__[key] = val
1533 self.dir.sconsign().set_entry(self.name, entry)
1535 def get_stored_info(self):
1538 stored = self.dir.sconsign().get_entry(self.name)
1539 except (KeyError, OSError):
1542 if isinstance(stored, BuildInfo):
1544 # The stored build information isn't a BuildInfo object.
1545 # This probably means it's an old SConsignEntry from SCons
1546 # 0.95 or before. The relevant attribute names are the same,
1547 # though, so just copy the attributes over to an object of
1550 for key, val in stored.__dict__.items():
1551 setattr(binfo, key, val)
1554 def get_stored_implicit(self):
1555 binfo = self.get_stored_info()
1556 try: implicit = binfo.bimplicit
1557 except AttributeError: return None
1558 else: return map(self.dir.Entry, implicit)
1560 def rel_path(self, other):
1561 return self.dir.rel_path(other)
1563 def get_found_includes(self, env, scanner, path):
1564 """Return the included implicit dependencies in this file.
1565 Cache results so we only scan the file once per path
1566 regardless of how many times this information is requested.
1570 return scanner(self, env, path)
1572 def _createDir(self):
1573 # ensure that the directories for this node are
1577 def retrieve_from_cache(self):
1578 """Try to retrieve the node's content from a cache
1580 This method is called from multiple threads in a parallel build,
1581 so only do thread safe stuff here. Do thread unsafe stuff in
1584 Note that there's a special trick here with the execute flag
1585 (one that's not normally done for other actions). Basically
1586 if the user requested a noexec (-n) build, then
1587 SCons.Action.execute_actions is set to 0 and when any action
1588 is called, it does its showing but then just returns zero
1589 instead of actually calling the action execution operation.
1590 The problem for caching is that if the file does NOT exist in
1591 cache then the CacheRetrieveString won't return anything to
1592 show for the task, but the Action.__call__ won't call
1593 CacheRetrieveFunc; instead it just returns zero, which makes
1594 the code below think that the file *was* successfully
1595 retrieved from the cache, therefore it doesn't do any
1596 subsequent building. However, the CacheRetrieveString didn't
1597 print anything because it didn't actually exist in the cache,
1598 and no more build actions will be performed, so the user just
1599 sees nothing. The fix is to tell Action.__call__ to always
1600 execute the CacheRetrieveFunc and then have the latter
1601 explicitly check SCons.Action.execute_actions itself.
1603 Returns true iff the node was successfully retrieved.
1605 b = self.is_derived()
1606 if not b and not self.has_src_builder():
1608 if b and self.fs.CachePath:
1609 if self.fs.cache_show:
1610 if CacheRetrieveSilent(self, [], None, execute=1) == 0:
1611 self.build(presub=0, execute=0)
1613 elif CacheRetrieve(self, [], None, execute=1) == 0:
1618 """Called just after this node is successfully built.
1620 # Push this file out to cache before the superclass Node.built()
1621 # method has a chance to clear the build signature, which it
1622 # will do if this file has a source scanner.
1623 if self.fs.CachePath and self.exists():
1624 CachePush(self, [], None)
1625 self.fs.clear_cache()
1626 SCons.Node.Node.built(self)
1629 if self.fs.CachePath and self.fs.cache_force and self.exists():
1630 CachePush(self, None, None)
1632 def has_src_builder(self):
1633 """Return whether this Node has a source builder or not.
1635 If this Node doesn't have an explicit source code builder, this
1636 is where we figure out, on the fly, if there's a transparent
1637 source code builder for it.
1639 Note that if we found a source builder, we also set the
1640 self.builder attribute, so that all of the methods that actually
1641 *build* this file don't have to do anything different.
1645 except AttributeError:
1649 scb = self.dir.src_builder()
1651 if self.dir.sccs_on_disk(self.name):
1652 scb = get_DefaultSCCSBuilder()
1653 elif self.dir.rcs_on_disk(self.name):
1654 scb = get_DefaultRCSBuilder()
1658 self.builder_set(scb)
1660 return not scb is None
1662 def alter_targets(self):
1663 """Return any corresponding targets in a build directory.
1665 if self.is_derived():
1667 return self.fs.build_dir_target_climb(self, self.dir, [self.name])
1669 def is_pseudo_derived(self):
1671 return self.has_src_builder()
1673 def _rmv_existing(self):
1675 Unlink(self, [], None)
1678 """Prepare for this file to be created."""
1679 SCons.Node.Node.prepare(self)
1681 if self.get_state() != SCons.Node.up_to_date:
1683 if self.is_derived() and not self.precious:
1684 self._rmv_existing()
1688 except SCons.Errors.StopError, drive:
1689 desc = "No drive `%s' for target `%s'." % (drive, self)
1690 raise SCons.Errors.StopError, desc
1693 """Remove this file."""
1694 if self.exists() or self.islink():
1695 self.fs.unlink(self.path)
1699 def do_duplicate(self, src):
1702 Unlink(self, None, None)
1703 except SCons.Errors.BuildError:
1706 Link(self, src, None)
1707 except SCons.Errors.BuildError, e:
1708 desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
1709 raise SCons.Errors.StopError, desc
1711 # The Link() action may or may not have actually
1712 # created the file, depending on whether the -n
1713 # option was used or not. Delete the _exists and
1714 # _rexists attributes so they can be reevaluated.
1719 # Duplicate from source path if we are set up to do this.
1720 if self.duplicate and not self.is_derived() and not self.linked:
1723 return Base.exists(self)
1725 if src.abspath != self.abspath and src.exists():
1726 self.do_duplicate(src)
1727 return Base.exists(self)
1729 def new_binfo(self):
1732 def del_cinfo(self):
1735 except AttributeError:
1738 del self.binfo.timestamp
1739 except AttributeError:
1742 def calc_csig(self, calc=None):
1744 Generate a node's content signature, the digested signature
1748 cache - alternate node to use for the signature cache
1749 returns - the content signature
1752 calc = self.calculator()
1755 return self.binfo.csig
1756 except AttributeError:
1759 if calc.max_drift >= 0:
1760 old = self.get_stored_info()
1765 mtime = self.get_timestamp()
1768 raise SCons.Errors.UserError, "no such %s" % self
1771 if (old.timestamp and old.csig and old.timestamp == mtime):
1772 # use the signature stored in the .sconsign file
1775 csig = calc.module.signature(self)
1776 except AttributeError:
1777 csig = calc.module.signature(self)
1779 if calc.max_drift >= 0 and (time.time() - mtime) > calc.max_drift:
1782 except AttributeError:
1783 binfo = self.binfo = self.new_binfo()
1785 binfo.timestamp = mtime
1786 self.store_info(binfo)
1790 def current(self, calc=None):
1791 self.binfo = self.gen_binfo(calc)
1795 if self.always_build:
1797 if not self.exists():
1798 # The file doesn't exist locally...
1801 # ...but there is one in a Repository...
1802 old = r.get_stored_info()
1803 if old == self.binfo:
1804 # ...and it's even up-to-date...
1806 # ...and they'd like a local copy.
1807 LocalCopy(self, r, None)
1808 self.store_info(self.binfo)
1812 old = self.get_stored_info()
1813 return (old == self.binfo)
1817 if not self.exists():
1818 norm_name = _my_normcase(self.name)
1819 for dir in self.dir.get_all_rdirs():
1820 try: node = dir.entries[norm_name]
1821 except KeyError: node = dir.file_on_disk(self.name)
1822 if node and node.exists() and \
1823 (isinstance(node, File) or isinstance(node, Entry) \
1824 or not node.is_derived()):
1829 return str(self.rfile())
1831 def cachepath(self):
1832 if not self.fs.CachePath:
1834 if self.binfo.bsig is None:
1835 raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
1836 # Add the path to the cache signature, because multiple
1837 # targets built by the same action will all have the same
1838 # build signature, and we have to differentiate them somehow.
1839 cache_sig = SCons.Sig.MD5.collect([self.binfo.bsig, self.path])
1840 subdir = string.upper(cache_sig[0])
1841 dir = os.path.join(self.fs.CachePath, subdir)
1842 return dir, os.path.join(dir, cache_sig)
1844 def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
1845 return self.dir.File(prefix + splitext(self.name)[0] + suffix)
1847 def must_be_a_Dir(self):
1848 """Called to make sure a Node is a Dir. Since we're already a
1849 File, this is a TypeError..."""
1850 raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
1854 def find_file(filename, paths, verbose=None):
1856 find_file(str, [Dir()]) -> [nodes]
1858 filename - a filename to find
1859 paths - a list of directory path *nodes* to search in. Can be
1860 represented as a list, a tuple, or a callable that is
1861 called with no arguments and returns the list or tuple.
1863 returns - the node created from the found file.
1865 Find a node corresponding to either a derived file or a file
1866 that exists already.
1868 Only the first file found is returned, and none is returned
1869 if no file is found.
1873 if not SCons.Util.is_String(verbose):
1874 verbose = "find_file"
1875 if not callable(verbose):
1876 verbose = ' %s: ' % verbose
1877 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
1879 verbose = lambda x: x
1884 # Give Entries a chance to morph into Dirs.
1885 paths = map(lambda p: p.must_be_a_Dir(), paths)
1887 filedir, filename = os.path.split(filename)
1889 def filedir_lookup(p, fd=filedir):
1893 # We tried to look up a Dir, but it seems there's already
1894 # a File (or something else) there. No big.
1896 paths = filter(None, map(filedir_lookup, paths))
1899 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
1900 node, d = dir.srcdir_find_file(filename)
1902 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
1906 def find_files(filenames, paths):
1908 find_files([str], [Dir()]) -> [nodes]
1910 filenames - a list of filenames to find
1911 paths - a list of directory path *nodes* to search in
1913 returns - the nodes created from the found files.
1915 Finds nodes corresponding to either derived files or files
1918 Only the first file found is returned for each filename,
1919 and any files that aren't found are ignored.
1921 nodes = map(lambda x, paths=paths: find_file(x, paths), filenames)
1922 return filter(None, nodes)