1 """scons.Node.FS
2
3 File system nodes.
4
5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
7
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.
10
11 """
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 from __future__ import print_function
35
36 __revision__ = "src/engine/SCons/Node/FS.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
37
38 import fnmatch
39 import os
40 import re
41 import shutil
42 import stat
43 import sys
44 import time
45 import codecs
46 from itertools import chain
47
48 import SCons.Action
49 import SCons.Debug
50 from SCons.Debug import logInstanceCreation
51 import SCons.Errors
52 import SCons.Memoize
53 import SCons.Node
54 import SCons.Node.Alias
55 import SCons.Subst
56 import SCons.Util
57 import SCons.Warnings
58
59 from SCons.Debug import Trace
60
61 print_duplicate = 0
62
63 MD5_TIMESTAMP_DEBUG = False
67 raise NotImplementedError
68
76
77 _sconsign_map = {0 : sconsign_none,
78 1 : sconsign_dir}
82
83 -class EntryProxyAttributeError(AttributeError):
84 """
85 An AttributeError subclass for recording and displaying the name
86 of the underlying Entry involved in an AttributeError exception.
87 """
88 - def __init__(self, entry_proxy, attribute):
89 AttributeError.__init__(self)
90 self.entry_proxy = entry_proxy
91 self.attribute = attribute
93 entry = self.entry_proxy.get()
94 fmt = "%s instance %s has no attribute %s"
95 return fmt % (entry.__class__.__name__,
96 repr(entry.name),
97 repr(self.attribute))
98
99
100
101 default_max_drift = 2*24*60*60
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 Save_Strings = None
126
127
128
129
130
131
132
133
134 do_splitdrive = None
135 _my_splitdrive =None
138 global do_splitdrive
139 global has_unc
140 drive, path = os.path.splitdrive('X:/foo')
141
142
143 has_unc = (hasattr(os.path, 'splitunc')
144 or os.path.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive')
145
146 do_splitdrive = not not drive or has_unc
147
148 global _my_splitdrive
149 if has_unc:
150 def splitdrive(p):
151 if p[1:2] == ':':
152 return p[:2], p[2:]
153 if p[0:2] == '//':
154
155
156 return '//', p[1:]
157 return '', p
158 else:
159 def splitdrive(p):
160 if p[1:2] == ':':
161 return p[:2], p[2:]
162 return '', p
163 _my_splitdrive = splitdrive
164
165
166
167 global OS_SEP
168 global UNC_PREFIX
169 global os_sep_is_slash
170
171 OS_SEP = os.sep
172 UNC_PREFIX = OS_SEP + OS_SEP
173 os_sep_is_slash = OS_SEP == '/'
174
175 initialize_do_splitdrive()
176
177
178 needs_normpath_check = re.compile(
179 r'''
180 # We need to renormalize the path if it contains any consecutive
181 # '/' characters.
182 .*// |
183
184 # We need to renormalize the path if it contains a '..' directory.
185 # Note that we check for all the following cases:
186 #
187 # a) The path is a single '..'
188 # b) The path starts with '..'. E.g. '../' or '../moredirs'
189 # but we not match '..abc/'.
190 # c) The path ends with '..'. E.g. '/..' or 'dirs/..'
191 # d) The path contains a '..' in the middle.
192 # E.g. dirs/../moredirs
193
194 (.*/)?\.\.(?:/|$) |
195
196 # We need to renormalize the path if it contains a '.'
197 # directory, but NOT if it is a single '.' '/' characters. We
198 # do not want to match a single '.' because this case is checked
199 # for explicitly since this is common enough case.
200 #
201 # Note that we check for all the following cases:
202 #
203 # a) We don't match a single '.'
204 # b) We match if the path starts with '.'. E.g. './' or
205 # './moredirs' but we not match '.abc/'.
206 # c) We match if the path ends with '.'. E.g. '/.' or
207 # 'dirs/.'
208 # d) We match if the path contains a '.' in the middle.
209 # E.g. dirs/./moredirs
210
211 \./|.*/\.(?:/|$)
212
213 ''',
214 re.VERBOSE
215 )
216 needs_normpath_match = needs_normpath_check.match
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237 if hasattr(os, 'link') and sys.platform != 'win32':
250 else:
251 _hardlink_func = None
252
253 if hasattr(os, 'symlink') and sys.platform != 'win32':
256 else:
257 _softlink_func = None
263
264
265 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
266 'hard-copy', 'soft-copy', 'copy']
267
268 Link_Funcs = []
292
294 """
295 Relative paths cause problems with symbolic links, so
296 we use absolute paths, which may be a problem for people
297 who want to move their soft-linked src-trees around. Those
298 people should use the 'hard-copy' mode, softlinks cannot be
299 used for that; at least I have no idea how ...
300 """
301 src = source[0].get_abspath()
302 dest = target[0].get_abspath()
303 dir, file = os.path.split(dest)
304 if dir and not target[0].fs.isdir(dir):
305 os.makedirs(dir)
306 if not Link_Funcs:
307
308 set_duplicate('hard-soft-copy')
309 fs = source[0].fs
310
311 for func in Link_Funcs:
312 try:
313 func(fs, src, dest)
314 break
315 except (IOError, OSError):
316
317
318
319
320
321
322 if func == Link_Funcs[-1]:
323
324 raise
325 return 0
326
327 Link = SCons.Action.Action(LinkFunc, None)
329 return 'Local copy of %s from %s' % (target[0], source[0])
330
331 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
337
338 Unlink = SCons.Action.Action(UnlinkFunc, None)
350
351 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
352
353 MkdirBuilder = None
369
372
373 _null = _Null()
374
375
376 _is_cygwin = sys.platform == "cygwin"
377 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
380 else:
383
388 self.type = type
389 self.do = do
390 self.ignore = ignore
391 self.func = do
393 return self.func(*args, **kw)
394 - def set(self, list):
395 if self.type in list:
396 self.func = self.do
397 else:
398 self.func = self.ignore
399
401 result = predicate()
402 try:
403
404
405
406
407
408
409 if node._memo['stat'] is None:
410 del node._memo['stat']
411 except (AttributeError, KeyError):
412 pass
413 if result:
414 raise TypeError(errorfmt % node.get_abspath())
415
418
419
420
421 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
422
423 diskcheckers = [
424 diskcheck_match,
425 ]
430
433
434
435
436 -class EntryProxy(SCons.Util.Proxy):
437
438 __str__ = SCons.Util.Delegate('__str__')
439
440
441
442
443 __hash__ = SCons.Util.Delegate('__hash__')
444
445 - def __get_abspath(self):
446 entry = self.get()
447 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
448 entry.name + "_abspath")
449
450 - def __get_filebase(self):
454
455 - def __get_suffix(self):
459
460 - def __get_file(self):
463
464 - def __get_base_path(self):
465 """Return the file's directory and file name, with the
466 suffix stripped."""
467 entry = self.get()
468 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
469 entry.name + "_base")
470
472 """Return the path with / as the path separator,
473 regardless of platform."""
474 if os_sep_is_slash:
475 return self
476 else:
477 entry = self.get()
478 r = entry.get_path().replace(OS_SEP, '/')
479 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
480
482 r"""Return the path with \ as the path separator,
483 regardless of platform."""
484 if OS_SEP == '\\':
485 return self
486 else:
487 entry = self.get()
488 r = entry.get_path().replace(OS_SEP, '\\')
489 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
490
491 - def __get_srcnode(self):
492 return EntryProxy(self.get().srcnode())
493
494 - def __get_srcdir(self):
495 """Returns the directory containing the source node linked to this
496 node via VariantDir(), or the directory of this node if not linked."""
497 return EntryProxy(self.get().srcnode().dir)
498
499 - def __get_rsrcnode(self):
500 return EntryProxy(self.get().srcnode().rfile())
501
502 - def __get_rsrcdir(self):
503 """Returns the directory containing the source node linked to this
504 node via VariantDir(), or the directory of this node if not linked."""
505 return EntryProxy(self.get().srcnode().rfile().dir)
506
507 - def __get_dir(self):
508 return EntryProxy(self.get().dir)
509
510 dictSpecialAttrs = { "base" : __get_base_path,
511 "posix" : __get_posix_path,
512 "windows" : __get_windows_path,
513 "win32" : __get_windows_path,
514 "srcpath" : __get_srcnode,
515 "srcdir" : __get_srcdir,
516 "dir" : __get_dir,
517 "abspath" : __get_abspath,
518 "filebase" : __get_filebase,
519 "suffix" : __get_suffix,
520 "file" : __get_file,
521 "rsrcpath" : __get_rsrcnode,
522 "rsrcdir" : __get_rsrcdir,
523 }
524
525 - def __getattr__(self, name):
526
527
528 try:
529 attr_function = self.dictSpecialAttrs[name]
530 except KeyError:
531 try:
532 attr = SCons.Util.Proxy.__getattr__(self, name)
533 except AttributeError:
534
535
536
537 raise EntryProxyAttributeError(self, name)
538 return attr
539 else:
540 return attr_function(self)
541
542
543 -class Base(SCons.Node.Node):
544 """A generic class for file system entries. This class is for
545 when we don't know yet whether the entry being looked up is a file
546 or a directory. Instances of this class can morph into either
547 Dir or File objects by a later, more precise lookup.
548
549 Note: this class does not define __cmp__ and __hash__ for
550 efficiency reasons. SCons does a lot of comparing of
551 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
552 as fast as possible, which means we want to use Python's built-in
553 object identity comparisons.
554 """
555
556 __slots__ = ['name',
557 'fs',
558 '_abspath',
559 '_labspath',
560 '_path',
561 '_tpath',
562 '_path_elements',
563 'dir',
564 'cwd',
565 'duplicate',
566 '_local',
567 'sbuilder',
568 '_proxy',
569 '_func_sconsign']
570
571 - def __init__(self, name, directory, fs):
606
608 return '"' + self.__str__() + '"'
609
611 """
612 This node, which already existed, is being looked up as the
613 specified klass. Raise an exception if it isn't.
614 """
615 if isinstance(self, klass) or klass is Entry:
616 return
617 raise TypeError("Tried to lookup %s '%s' as a %s." %\
618 (self.__class__.__name__, self.get_internal_path(), klass.__name__))
619
622
625
628
630 """ Together with the node_bwcomp dict defined below,
631 this method provides a simple backward compatibility
632 layer for the Node attributes 'abspath', 'labspath',
633 'path', 'tpath', 'suffix' and 'path_elements'. These Node
634 attributes used to be directly available in v2.3 and earlier, but
635 have been replaced by getter methods that initialize the
636 single variables lazily when required, in order to save memory.
637 The redirection to the getters lets older Tools and
638 SConstruct continue to work without any additional changes,
639 fully transparent to the user.
640 Note, that __getattr__ is only called as fallback when the
641 requested attribute can't be found, so there should be no
642 speed performance penalty involved for standard builds.
643 """
644 if attr in node_bwcomp:
645 return node_bwcomp[attr](self)
646
647 raise AttributeError("%r object has no attribute %r" %
648 (self.__class__, attr))
649
657
659 """ less than operator used by sorting on py3"""
660 return str(self) < str(other)
661
662 @SCons.Memoize.CountMethodCall
671
696
697 rstr = __str__
698
699 @SCons.Memoize.CountMethodCall
712
715
718
720 st = self.stat()
721 if st:
722 return st[stat.ST_MTIME]
723 else:
724 return None
725
727 st = self.stat()
728 if st:
729 return st[stat.ST_SIZE]
730 else:
731 return None
732
734 st = self.stat()
735 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
736
738 st = self.stat()
739 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
740
741 if hasattr(os, 'symlink'):
746 else:
749
755
758
770
772 """Return path relative to the current working directory of the
773 Node.FS.Base object that owns us."""
774 if not dir:
775 dir = self.fs.getcwd()
776 if self == dir:
777 return '.'
778 path_elems = self.get_path_elements()
779 pathname = ''
780 try: i = path_elems.index(dir)
781 except ValueError:
782 for p in path_elems[:-1]:
783 pathname += p.dirname
784 else:
785 for p in path_elems[i+1:-1]:
786 pathname += p.dirname
787 return pathname + path_elems[-1].name
788
794
796 """Fetch the source code builder for this node.
797
798 If there isn't one, we cache the source code builder specified
799 for the directory (which in turn will cache the value from its
800 parent directory, and so on up to the file system root).
801 """
802 try:
803 scb = self.sbuilder
804 except AttributeError:
805 scb = self.dir.src_builder()
806 self.sbuilder = scb
807 return scb
808
812
816
822
828
831
833
834
835
836 return self.name
837
839 try:
840 return self._proxy
841 except AttributeError:
842 ret = EntryProxy(self)
843 self._proxy = ret
844 return ret
845
847 """
848
849 Generates a target entry that corresponds to this entry (usually
850 a source file) with the specified prefix and suffix.
851
852 Note that this method can be overridden dynamically for generated
853 files that need different behavior. See Tool/swig.py for
854 an example.
855 """
856 return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext)
857
860
861 @SCons.Memoize.CountDictCall(_Rfindalldirs_key)
863 """
864 Return all of the directories for a given path list, including
865 corresponding "backing" directories in any repositories.
866
867 The Node lookups are relative to this Node (typically a
868 directory), so memoizing result saves cycles from looking
869 up the same path for each target in a given directory.
870 """
871 try:
872 memo_dict = self._memo['Rfindalldirs']
873 except KeyError:
874 memo_dict = {}
875 self._memo['Rfindalldirs'] = memo_dict
876 else:
877 try:
878 return memo_dict[pathlist]
879 except KeyError:
880 pass
881
882 create_dir_relative_to_self = self.Dir
883 result = []
884 for path in pathlist:
885 if isinstance(path, SCons.Node.Node):
886 result.append(path)
887 else:
888 dir = create_dir_relative_to_self(path)
889 result.extend(dir.get_all_rdirs())
890
891 memo_dict[pathlist] = result
892
893 return result
894
895 - def RDirs(self, pathlist):
896 """Search for a list of directories in the Repository list."""
897 cwd = self.cwd or self.fs._cwd
898 return cwd.Rfindalldirs(pathlist)
899
900 @SCons.Memoize.CountMethodCall
902 try:
903 return self._memo['rentry']
904 except KeyError:
905 pass
906 result = self
907 if not self.exists():
908 norm_name = _my_normcase(self.name)
909 for dir in self.dir.get_all_rdirs():
910 try:
911 node = dir.entries[norm_name]
912 except KeyError:
913 if dir.entry_exists_on_disk(self.name):
914 result = dir.Entry(self.name)
915 break
916 self._memo['rentry'] = result
917 return result
918
919 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
921
922
923
924
925
926 node_bwcomp = {'abspath' : Base.get_abspath,
927 'labspath' : Base.get_labspath,
928 'path' : Base.get_internal_path,
929 'tpath' : Base.get_tpath,
930 'path_elements' : Base.get_path_elements,
931 'suffix' : Base.get_suffix}
932
933 -class Entry(Base):
934 """This is the class for generic Node.FS entries--that is, things
935 that could be a File or a Dir, but we're just not sure yet.
936 Consequently, the methods in this class really exist just to
937 transform their associated object into the right class when the
938 time comes, and then call the same-named method in the transformed
939 class."""
940
941 __slots__ = ['scanner_paths',
942 'cachedir_csig',
943 'cachesig',
944 'repositories',
945 'srcdir',
946 'entries',
947 'searched',
948 '_sconsign',
949 'variant_dirs',
950 'root',
951 'dirname',
952 'on_disk_entries',
953 'released_target_info',
954 'contentsig']
955
956 - def __init__(self, name, directory, fs):
957 Base.__init__(self, name, directory, fs)
958 self._func_exists = 3
959 self._func_get_contents = 1
960
961 - def diskcheck_match(self):
963
964 - def disambiguate(self, must_exist=None):
965 """
966 """
967 if self.isdir():
968 self.__class__ = Dir
969 self._morph()
970 elif self.isfile():
971 self.__class__ = File
972 self._morph()
973 self.clear()
974 else:
975
976
977
978
979
980
981
982
983
984 srcdir = self.dir.srcnode()
985 if srcdir != self.dir and \
986 srcdir.entry_exists_on_disk(self.name) and \
987 self.srcnode().isdir():
988 self.__class__ = Dir
989 self._morph()
990 elif must_exist:
991 msg = "No such file or directory: '%s'" % self.get_abspath()
992 raise SCons.Errors.UserError(msg)
993 else:
994 self.__class__ = File
995 self._morph()
996 self.clear()
997 return self
998
1000 """We're a generic Entry, but the caller is actually looking for
1001 a File at this point, so morph into one."""
1002 self.__class__ = File
1003 self._morph()
1004 self.clear()
1005 return File.rfile(self)
1006
1007 - def scanner_key(self):
1008 return self.get_suffix()
1009
1010 - def get_contents(self):
1011 """Fetch the contents of the entry. Returns the exact binary
1012 contents of the file."""
1013 return SCons.Node._get_contents_map[self._func_get_contents](self)
1014
1016 """Fetch the decoded text contents of a Unicode encoded Entry.
1017
1018 Since this should return the text contents from the file
1019 system, we check to see into what sort of subclass we should
1020 morph this Entry."""
1021 try:
1022 self = self.disambiguate(must_exist=1)
1023 except SCons.Errors.UserError:
1024
1025
1026
1027
1028
1029 return ''
1030 else:
1031 return self.get_text_contents()
1032
1033 - def must_be_same(self, klass):
1034 """Called to make sure a Node is a Dir. Since we're an
1035 Entry, we can morph into one."""
1036 if self.__class__ is not klass:
1037 self.__class__ = klass
1038 self._morph()
1039 self.clear()
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1054
1055 - def rel_path(self, other):
1056 d = self.disambiguate()
1057 if d.__class__ is Entry:
1058 raise Exception("rel_path() could not disambiguate File/Dir")
1059 return d.rel_path(other)
1060
1061 - def new_ninfo(self):
1062 return self.disambiguate().new_ninfo()
1063
1064 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1065 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1066
1067 - def get_subst_proxy(self):
1069
1070
1071
1072 _classEntry = Entry
1076 """
1077 This class implements an abstraction layer for operations involving
1078 a local file system. Essentially, this wraps any function in
1079 the os, os.path or shutil modules that we use to actually go do
1080 anything with or to the local file system.
1081
1082 Note that there's a very good chance we'll refactor this part of
1083 the architecture in some way as we really implement the interface(s)
1084 for remote file system Nodes. For example, the right architecture
1085 might be to have this be a subclass instead of a base class.
1086 Nevertheless, we're using this as a first step in that direction.
1087
1088 We're not using chdir() yet because the calling subclass method
1089 needs to use os.chdir() directly to avoid recursion. Will we
1090 really need this one?
1091 """
1092
1093
1094 - def chmod(self, path, mode):
1096 - def copy(self, src, dst):
1097 return shutil.copy(src, dst)
1098 - def copy2(self, src, dst):
1099 return shutil.copy2(src, dst)
1110 - def link(self, src, dst):
1111 return os.link(src, dst)
1121 return os.rename(old, new)
1122 - def stat(self, path):
1126 - def open(self, path):
1130
1131 if hasattr(os, 'symlink'):
1134 else:
1137
1138 if hasattr(os, 'readlink'):
1141 else:
1144
1145
1146 -class FS(LocalFS):
1147
1149 """Initialize the Node.FS subsystem.
1150
1151 The supplied path is the top of the source tree, where we
1152 expect to find the top-level build file. If no path is
1153 supplied, the current directory is the default.
1154
1155 The path argument must be a valid absolute path.
1156 """
1157 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS')
1158
1159 self._memo = {}
1160
1161 self.Root = {}
1162 self.SConstruct_dir = None
1163 self.max_drift = default_max_drift
1164
1165 self.Top = None
1166 if path is None:
1167 self.pathTop = os.getcwd()
1168 else:
1169 self.pathTop = path
1170 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1171
1172 self.Top = self.Dir(self.pathTop)
1173 self.Top._path = '.'
1174 self.Top._tpath = '.'
1175 self._cwd = self.Top
1176
1177 DirNodeInfo.fs = self
1178 FileNodeInfo.fs = self
1179
1181 self.SConstruct_dir = dir
1182
1184 return self.max_drift
1185
1187 self.max_drift = max_drift
1188
1190 if hasattr(self, "_cwd"):
1191 return self._cwd
1192 else:
1193 return "<no cwd>"
1194
1195 - def chdir(self, dir, change_os_dir=0):
1196 """Change the current working directory for lookups.
1197 If change_os_dir is true, we will also change the "real" cwd
1198 to match.
1199 """
1200 curr=self._cwd
1201 try:
1202 if dir is not None:
1203 self._cwd = dir
1204 if change_os_dir:
1205 os.chdir(dir.get_abspath())
1206 except OSError:
1207 self._cwd = curr
1208 raise
1209
1211 """
1212 Returns the root directory for the specified drive, creating
1213 it if necessary.
1214 """
1215 drive = _my_normcase(drive)
1216 try:
1217 return self.Root[drive]
1218 except KeyError:
1219 root = RootDir(drive, self)
1220 self.Root[drive] = root
1221 if not drive:
1222 self.Root[self.defaultDrive] = root
1223 elif drive == self.defaultDrive:
1224 self.Root[''] = root
1225 return root
1226
1227 - def _lookup(self, p, directory, fsclass, create=1):
1228 """
1229 The generic entry point for Node lookup with user-supplied data.
1230
1231 This translates arbitrary input into a canonical Node.FS object
1232 of the specified fsclass. The general approach for strings is
1233 to turn it into a fully normalized absolute path and then call
1234 the root directory's lookup_abs() method for the heavy lifting.
1235
1236 If the path name begins with '#', it is unconditionally
1237 interpreted relative to the top-level directory of this FS. '#'
1238 is treated as a synonym for the top-level SConstruct directory,
1239 much like '~' is treated as a synonym for the user's home
1240 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1241 to the 'foo' subdirectory underneath the top-level SConstruct
1242 directory.
1243
1244 If the path name is relative, then the path is looked up relative
1245 to the specified directory, or the current directory (self._cwd,
1246 typically the SConscript directory) if the specified directory
1247 is None.
1248 """
1249 if isinstance(p, Base):
1250
1251
1252 p.must_be_same(fsclass)
1253 return p
1254
1255 p = str(p)
1256
1257 if not os_sep_is_slash:
1258 p = p.replace(OS_SEP, '/')
1259
1260 if p[0:1] == '#':
1261
1262
1263
1264 p = p[1:]
1265 directory = self.Top
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277 if do_splitdrive:
1278 drive, p = _my_splitdrive(p)
1279 if drive:
1280 root = self.get_root(drive)
1281 else:
1282 root = directory.root
1283 else:
1284 root = directory.root
1285
1286
1287
1288 p = p.strip('/')
1289
1290 needs_normpath = needs_normpath_match(p)
1291
1292
1293 if p in ('', '.'):
1294 p = directory.get_labspath()
1295 else:
1296 p = directory.get_labspath() + '/' + p
1297 else:
1298 if do_splitdrive:
1299 drive, p = _my_splitdrive(p)
1300 if drive and not p:
1301
1302
1303
1304 p = '/'
1305 else:
1306 drive = ''
1307
1308
1309
1310 if p != '/':
1311 p = p.rstrip('/')
1312
1313 needs_normpath = needs_normpath_match(p)
1314
1315 if p[0:1] == '/':
1316
1317 root = self.get_root(drive)
1318 else:
1319
1320
1321
1322
1323 if directory:
1324 if not isinstance(directory, Dir):
1325 directory = self.Dir(directory)
1326 else:
1327 directory = self._cwd
1328
1329 if p in ('', '.'):
1330 p = directory.get_labspath()
1331 else:
1332 p = directory.get_labspath() + '/' + p
1333
1334 if drive:
1335 root = self.get_root(drive)
1336 else:
1337 root = directory.root
1338
1339 if needs_normpath is not None:
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349 ins = p.split('/')[1:]
1350 outs = []
1351 for d in ins:
1352 if d == '..':
1353 try:
1354 outs.pop()
1355 except IndexError:
1356 pass
1357 elif d not in ('', '.'):
1358 outs.append(d)
1359 p = '/' + '/'.join(outs)
1360
1361 return root._lookup_abs(p, fsclass, create)
1362
1363 - def Entry(self, name, directory = None, create = 1):
1364 """Look up or create a generic Entry node with the specified name.
1365 If the name is a relative path (begins with ./, ../, or a file
1366 name), then it is looked up relative to the supplied directory
1367 node, or to the top level directory of the FS (supplied at
1368 construction time) if no directory is supplied.
1369 """
1370 return self._lookup(name, directory, Entry, create)
1371
1372 - def File(self, name, directory = None, create = 1):
1373 """Look up or create a File node with the specified name. If
1374 the name is a relative path (begins with ./, ../, or a file name),
1375 then it is looked up relative to the supplied directory node,
1376 or to the top level directory of the FS (supplied at construction
1377 time) if no directory is supplied.
1378
1379 This method will raise TypeError if a directory is found at the
1380 specified path.
1381 """
1382 return self._lookup(name, directory, File, create)
1383
1384 - def Dir(self, name, directory = None, create = True):
1385 """Look up or create a Dir node with the specified name. If
1386 the name is a relative path (begins with ./, ../, or a file name),
1387 then it is looked up relative to the supplied directory node,
1388 or to the top level directory of the FS (supplied at construction
1389 time) if no directory is supplied.
1390
1391 This method will raise TypeError if a normal file is found at the
1392 specified path.
1393 """
1394 return self._lookup(name, directory, Dir, create)
1395
1396 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1397 """Link the supplied variant directory to the source directory
1398 for purposes of building files."""
1399
1400 if not isinstance(src_dir, SCons.Node.Node):
1401 src_dir = self.Dir(src_dir)
1402 if not isinstance(variant_dir, SCons.Node.Node):
1403 variant_dir = self.Dir(variant_dir)
1404 if src_dir.is_under(variant_dir):
1405 raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
1406 if variant_dir.srcdir:
1407 if variant_dir.srcdir == src_dir:
1408 return
1409 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
1410 variant_dir.link(src_dir, duplicate)
1411
1418
1420 r"""Locate the directory of a given python module name
1421
1422 For example scons might resolve to
1423 Windows: C:\Python27\Lib\site-packages\scons-2.5.1
1424 Linux: /usr/lib/scons
1425
1426 This can be useful when we want to determine a toolpath based on a python module name"""
1427
1428 dirpath = ''
1429 if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] in (0,1,2,3,4)):
1430
1431 import imp
1432 splitname = modulename.split('.')
1433 srchpths = sys.path
1434 for item in splitname:
1435 file, path, desc = imp.find_module(item, srchpths)
1436 if file is not None:
1437 path = os.path.dirname(path)
1438 srchpths = [path]
1439 dirpath = path
1440 else:
1441
1442 import importlib.util
1443 modspec = importlib.util.find_spec(modulename)
1444 dirpath = os.path.dirname(modspec.origin)
1445 return self._lookup(dirpath, None, Dir, True)
1446
1447
1449 """Create targets in corresponding variant directories
1450
1451 Climb the directory tree, and look up path names
1452 relative to any linked variant directories we find.
1453
1454 Even though this loops and walks up the tree, we don't memoize
1455 the return value because this is really only used to process
1456 the command-line targets.
1457 """
1458 targets = []
1459 message = None
1460 fmt = "building associated VariantDir targets: %s"
1461 start_dir = dir
1462 while dir:
1463 for bd in dir.variant_dirs:
1464 if start_dir.is_under(bd):
1465
1466 return [orig], fmt % str(orig)
1467 p = os.path.join(bd._path, *tail)
1468 targets.append(self.Entry(p))
1469 tail = [dir.name] + tail
1470 dir = dir.up()
1471 if targets:
1472 message = fmt % ' '.join(map(str, targets))
1473 return targets, message
1474
1475 - def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
1476 """
1477 Globs
1478
1479 This is mainly a shim layer
1480 """
1481 if cwd is None:
1482 cwd = self.getcwd()
1483 return cwd.glob(pathname, ondisk, source, strings, exclude)
1484
1502
1506
1507 glob_magic_check = re.compile('[*?[]')
1511
1513 """A class for directories in a file system.
1514 """
1515
1516 __slots__ = ['scanner_paths',
1517 'cachedir_csig',
1518 'cachesig',
1519 'repositories',
1520 'srcdir',
1521 'entries',
1522 'searched',
1523 '_sconsign',
1524 'variant_dirs',
1525 'root',
1526 'dirname',
1527 'on_disk_entries',
1528 'released_target_info',
1529 'contentsig']
1530
1531 NodeInfo = DirNodeInfo
1532 BuildInfo = DirBuildInfo
1533
1534 - def __init__(self, name, directory, fs):
1538
1604
1608
1610 """Called when we change the repository(ies) for a directory.
1611 This clears any cached information that is invalidated by changing
1612 the repository."""
1613
1614 for node in list(self.entries.values()):
1615 if node != self.dir:
1616 if node != self and isinstance(node, Dir):
1617 node.__clearRepositoryCache(duplicate)
1618 else:
1619 node.clear()
1620 try:
1621 del node._srcreps
1622 except AttributeError:
1623 pass
1624 if duplicate is not None:
1625 node.duplicate=duplicate
1626
1630
1631 - def Entry(self, name):
1632 """
1633 Looks up or creates an entry node named 'name' relative to
1634 this directory.
1635 """
1636 return self.fs.Entry(name, self)
1637
1638 - def Dir(self, name, create=True):
1639 """
1640 Looks up or creates a directory node named 'name' relative to
1641 this directory.
1642 """
1643 return self.fs.Dir(name, self, create)
1644
1645 - def File(self, name):
1646 """
1647 Looks up or creates a file node named 'name' relative to
1648 this directory.
1649 """
1650 return self.fs.File(name, self)
1651
1652 - def link(self, srcdir, duplicate):
1659
1666
1667 @SCons.Memoize.CountMethodCall
1689
1695
1698
1701
1702 @SCons.Memoize.CountDictCall(_rel_path_key)
1704 """Return a path to "other" relative to this directory.
1705 """
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717 try:
1718 memo_dict = self._memo['rel_path']
1719 except KeyError:
1720 memo_dict = {}
1721 self._memo['rel_path'] = memo_dict
1722 else:
1723 try:
1724 return memo_dict[other]
1725 except KeyError:
1726 pass
1727
1728 if self is other:
1729 result = '.'
1730
1731 elif other not in self._path_elements:
1732 try:
1733 other_dir = other.get_dir()
1734 except AttributeError:
1735 result = str(other)
1736 else:
1737 if other_dir is None:
1738 result = other.name
1739 else:
1740 dir_rel_path = self.rel_path(other_dir)
1741 if dir_rel_path == '.':
1742 result = other.name
1743 else:
1744 result = dir_rel_path + OS_SEP + other.name
1745 else:
1746 i = self._path_elements.index(other) + 1
1747
1748 path_elems = ['..'] * (len(self._path_elements) - i) \
1749 + [n.name for n in other._path_elements[i:]]
1750
1751 result = OS_SEP.join(path_elems)
1752
1753 memo_dict[other] = result
1754
1755 return result
1756
1760
1764
1766 """Return this directory's implicit dependencies.
1767
1768 We don't bother caching the results because the scan typically
1769 shouldn't be requested more than once (as opposed to scanning
1770 .h file contents, which can be requested as many times as the
1771 files is #included by other files).
1772 """
1773 if not scanner:
1774 return []
1775
1776
1777
1778
1779
1780
1781
1782
1783 self.clear()
1784 return scanner(self, env, path)
1785
1786
1787
1788
1789
1792
1798
1799
1800
1801
1802
1804 """Create this directory, silently and without worrying about
1805 whether the builder is the default or not."""
1806 listDirs = []
1807 parent = self
1808 while parent:
1809 if parent.exists():
1810 break
1811 listDirs.append(parent)
1812 p = parent.up()
1813 if p is None:
1814
1815
1816 raise SCons.Errors.StopError(parent._path)
1817 parent = p
1818 listDirs.reverse()
1819 for dirnode in listDirs:
1820 try:
1821
1822
1823
1824
1825 SCons.Node.Node.build(dirnode)
1826 dirnode.get_executor().nullify()
1827
1828
1829
1830
1831 dirnode.clear()
1832 except OSError:
1833 pass
1834
1838
1840 """Return any corresponding targets in a variant directory.
1841 """
1842 return self.fs.variant_dir_target_climb(self, self, [])
1843
1845 """A directory does not get scanned."""
1846 return None
1847
1849 """We already emit things in text, so just return the binary
1850 version."""
1851 return self.get_contents()
1852
1853 - def get_contents(self):
1854 """Return content signatures and names of all our children
1855 separated by new-lines. Ensure that the nodes are sorted."""
1856 return SCons.Node._get_contents_map[self._func_get_contents](self)
1857
1859 """Compute the content signature for Directory nodes. In
1860 general, this is not needed and the content signature is not
1861 stored in the DirNodeInfo. However, if get_contents on a Dir
1862 node is called which has a child directory, the child
1863 directory should return the hash of its contents."""
1864 contents = self.get_contents()
1865 return SCons.Util.MD5signature(contents)
1866
1869
1880
1891
1895
1897 """Dir has a special need for srcnode()...if we
1898 have a srcdir attribute set, then that *is* our srcnode."""
1899 if self.srcdir:
1900 return self.srcdir
1901 return Base.srcnode(self)
1902
1904 """Return the latest timestamp from among our children"""
1905 stamp = 0
1906 for kid in self.children():
1907 if kid.get_timestamp() > stamp:
1908 stamp = kid.get_timestamp()
1909 return stamp
1910
1912 """Get the absolute path of the file."""
1913 return self._abspath
1914
1916 """Get the absolute path of the file."""
1917 return self._labspath
1918
1921
1924
1927
1928 - def entry_abspath(self, name):
1929 return self._abspath + OS_SEP + name
1930
1931 - def entry_labspath(self, name):
1932 return self._labspath + '/' + name
1933
1934 - def entry_path(self, name):
1935 return self._path + OS_SEP + name
1936
1937 - def entry_tpath(self, name):
1938 return self._tpath + OS_SEP + name
1939
1940 - def entry_exists_on_disk(self, name):
1941 """ Searches through the file/dir entries of the current
1942 directory, and returns True if a physical entry with the given
1943 name could be found.
1944
1945 @see rentry_exists_on_disk
1946 """
1947 try:
1948 d = self.on_disk_entries
1949 except AttributeError:
1950 d = {}
1951 try:
1952 entries = os.listdir(self._abspath)
1953 except OSError:
1954 pass
1955 else:
1956 for entry in map(_my_normcase, entries):
1957 d[entry] = True
1958 self.on_disk_entries = d
1959 if sys.platform == 'win32' or sys.platform == 'cygwin':
1960 name = _my_normcase(name)
1961 result = d.get(name)
1962 if result is None:
1963
1964
1965 result = os.path.exists(self._abspath + OS_SEP + name)
1966 d[name] = result
1967 return result
1968 else:
1969 return name in d
1970
1971 - def rentry_exists_on_disk(self, name):
1972 """ Searches through the file/dir entries of the current
1973 *and* all its remote directories (repos), and returns
1974 True if a physical entry with the given name could be found.
1975 The local directory (self) gets searched first, so
1976 repositories take a lower precedence regarding the
1977 searching order.
1978
1979 @see entry_exists_on_disk
1980 """
1981
1982 rentry_exists = self.entry_exists_on_disk(name)
1983 if not rentry_exists:
1984
1985 norm_name = _my_normcase(name)
1986 for rdir in self.get_all_rdirs():
1987 try:
1988 node = rdir.entries[norm_name]
1989 if node:
1990 rentry_exists = True
1991 break
1992 except KeyError:
1993 if rdir.entry_exists_on_disk(name):
1994 rentry_exists = True
1995 break
1996 return rentry_exists
1997
1998 @SCons.Memoize.CountMethodCall
2018
2035
2038
2039 @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
2041 try:
2042 memo_dict = self._memo['srcdir_find_file']
2043 except KeyError:
2044 memo_dict = {}
2045 self._memo['srcdir_find_file'] = memo_dict
2046 else:
2047 try:
2048 return memo_dict[filename]
2049 except KeyError:
2050 pass
2051
2052 def func(node):
2053 if (isinstance(node, File) or isinstance(node, Entry)) and \
2054 (node.is_derived() or node.exists()):
2055 return node
2056 return None
2057
2058 norm_name = _my_normcase(filename)
2059
2060 for rdir in self.get_all_rdirs():
2061 try: node = rdir.entries[norm_name]
2062 except KeyError: node = rdir.file_on_disk(filename)
2063 else: node = func(node)
2064 if node:
2065 result = (node, self)
2066 memo_dict[filename] = result
2067 return result
2068
2069 for srcdir in self.srcdir_list():
2070 for rdir in srcdir.get_all_rdirs():
2071 try: node = rdir.entries[norm_name]
2072 except KeyError: node = rdir.file_on_disk(filename)
2073 else: node = func(node)
2074 if node:
2075 result = (File(filename, self, self.fs), srcdir)
2076 memo_dict[filename] = result
2077 return result
2078
2079 result = (None, None)
2080 memo_dict[filename] = result
2081 return result
2082
2091
2100
2101 - def walk(self, func, arg):
2102 """
2103 Walk this directory tree by calling the specified function
2104 for each directory in the tree.
2105
2106 This behaves like the os.path.walk() function, but for in-memory
2107 Node.FS.Dir objects. The function takes the same arguments as
2108 the functions passed to os.path.walk():
2109
2110 func(arg, dirname, fnames)
2111
2112 Except that "dirname" will actually be the directory *Node*,
2113 not the string. The '.' and '..' entries are excluded from
2114 fnames. The fnames list may be modified in-place to filter the
2115 subdirectories visited or otherwise impose a specific order.
2116 The "arg" argument is always passed to func() and may be used
2117 in any way (or ignored, passing None is common).
2118 """
2119 entries = self.entries
2120 names = list(entries.keys())
2121 names.remove('.')
2122 names.remove('..')
2123 func(arg, self, names)
2124 for dirname in [n for n in names if isinstance(entries[n], Dir)]:
2125 entries[dirname].walk(func, arg)
2126
2127 - def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
2128 """
2129 Returns a list of Nodes (or strings) matching a specified
2130 pathname pattern.
2131
2132 Pathname patterns follow UNIX shell semantics: * matches
2133 any-length strings of any characters, ? matches any character,
2134 and [] can enclose lists or ranges of characters. Matches do
2135 not span directory separators.
2136
2137 The matches take into account Repositories, returning local
2138 Nodes if a corresponding entry exists in a Repository (either
2139 an in-memory Node or something on disk).
2140
2141 By defafult, the glob() function matches entries that exist
2142 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
2143 argument to False (or some other non-true value) causes the glob()
2144 function to only match in-memory Nodes. The default behavior is
2145 to return both the on-disk and in-memory Nodes.
2146
2147 The "source" argument, when true, specifies that corresponding
2148 source Nodes must be returned if you're globbing in a build
2149 directory (initialized with VariantDir()). The default behavior
2150 is to return Nodes local to the VariantDir().
2151
2152 The "strings" argument, when true, returns the matches as strings,
2153 not Nodes. The strings are path names relative to this directory.
2154
2155 The "exclude" argument, if not None, must be a pattern or a list
2156 of patterns following the same UNIX shell semantics.
2157 Elements matching a least one pattern of this list will be excluded
2158 from the result.
2159
2160 The underlying algorithm is adapted from the glob.glob() function
2161 in the Python library (but heavily modified), and uses fnmatch()
2162 under the covers.
2163 """
2164 dirname, basename = os.path.split(pathname)
2165 if not dirname:
2166 result = self._glob1(basename, ondisk, source, strings)
2167 else:
2168 if has_glob_magic(dirname):
2169 list = self.glob(dirname, ondisk, source, False, exclude)
2170 else:
2171 list = [self.Dir(dirname, create=True)]
2172 result = []
2173 for dir in list:
2174 r = dir._glob1(basename, ondisk, source, strings)
2175 if strings:
2176 r = [os.path.join(str(dir), x) for x in r]
2177 result.extend(r)
2178 if exclude:
2179 excludes = []
2180 excludeList = SCons.Util.flatten(exclude)
2181 for x in excludeList:
2182 r = self.glob(x, ondisk, source, strings)
2183 excludes.extend(r)
2184 result = [x for x in result if not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes))]
2185 return sorted(result, key=lambda a: str(a))
2186
2187 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2188 """
2189 Globs for and returns a list of entry names matching a single
2190 pattern in this directory.
2191
2192 This searches any repositories and source directories for
2193 corresponding entries and returns a Node (or string) relative
2194 to the current directory if an entry is found anywhere.
2195
2196 TODO: handle pattern with no wildcard
2197 """
2198 search_dir_list = self.get_all_rdirs()
2199 for srcdir in self.srcdir_list():
2200 search_dir_list.extend(srcdir.get_all_rdirs())
2201
2202 selfEntry = self.Entry
2203 names = []
2204 for dir in search_dir_list:
2205
2206
2207
2208 node_names = [ v.name for k, v in dir.entries.items()
2209 if k not in ('.', '..') ]
2210 names.extend(node_names)
2211 if not strings:
2212
2213
2214 for name in node_names: selfEntry(name)
2215 if ondisk:
2216 try:
2217 disk_names = os.listdir(dir._abspath)
2218 except os.error:
2219 continue
2220 names.extend(disk_names)
2221 if not strings:
2222
2223
2224
2225
2226
2227
2228
2229
2230 if pattern[0] != '.':
2231 disk_names = [x for x in disk_names if x[0] != '.']
2232 disk_names = fnmatch.filter(disk_names, pattern)
2233 dirEntry = dir.Entry
2234 for name in disk_names:
2235
2236
2237 name = './' + name
2238 node = dirEntry(name).disambiguate()
2239 n = selfEntry(name)
2240 if n.__class__ != node.__class__:
2241 n.__class__ = node.__class__
2242 n._morph()
2243
2244 names = set(names)
2245 if pattern[0] != '.':
2246 names = [x for x in names if x[0] != '.']
2247 names = fnmatch.filter(names, pattern)
2248
2249 if strings:
2250 return names
2251
2252 return [self.entries[_my_normcase(n)] for n in names]
2253
2255 """A class for the root directory of a file system.
2256
2257 This is the same as a Dir class, except that the path separator
2258 ('/' or '\\') is actually part of the name, so we don't need to
2259 add a separator when creating the path names of entries within
2260 this directory.
2261 """
2262
2263 __slots__ = ('_lookupDict', )
2264
2318
2360
2361
2366
2368 """
2369 Fast (?) lookup of a *normalized* absolute path.
2370
2371 This method is intended for use by internal lookups with
2372 already-normalized path data. For general-purpose lookups,
2373 use the FS.Entry(), FS.Dir() or FS.File() methods.
2374
2375 The caller is responsible for making sure we're passed a
2376 normalized absolute path; we merely let Python's dictionary look
2377 up and return the One True Node.FS object for the path.
2378
2379 If a Node for the specified "p" doesn't already exist, and
2380 "create" is specified, the Node may be created after recursive
2381 invocation to find or create the parent directory or directories.
2382 """
2383 k = _my_normcase(p)
2384 try:
2385 result = self._lookupDict[k]
2386 except KeyError:
2387 if not create:
2388 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2389 raise SCons.Errors.UserError(msg)
2390
2391
2392 dir_name, file_name = p.rsplit('/',1)
2393 dir_node = self._lookup_abs(dir_name, Dir)
2394 result = klass(file_name, dir_node, self.fs)
2395
2396
2397
2398 result.diskcheck_match()
2399
2400 self._lookupDict[k] = result
2401 dir_node.entries[_my_normcase(file_name)] = result
2402 dir_node.implicit = None
2403 else:
2404
2405
2406 result.must_be_same(klass)
2407 return result
2408
2411
2412 - def entry_abspath(self, name):
2413 return self._abspath + name
2414
2415 - def entry_labspath(self, name):
2417
2418 - def entry_path(self, name):
2419 return self._path + name
2420
2421 - def entry_tpath(self, name):
2422 return self._tpath + name
2423
2425 if self is dir:
2426 return 1
2427 else:
2428 return 0
2429
2432
2435
2438
2441 __slots__ = ('csig', 'timestamp', 'size')
2442 current_version_id = 2
2443
2444 field_list = ['csig', 'timestamp', 'size']
2445
2446
2447 fs = None
2448
2459
2461 """
2462 Return all fields that shall be pickled. Walk the slots in the class
2463 hierarchy and add those to the state dictionary. If a '__dict__' slot is
2464 available, copy all entries to the dictionary. Also include the version
2465 id, which is fixed for all instances of a class.
2466 """
2467 state = getattr(self, '__dict__', {}).copy()
2468 for obj in type(self).mro():
2469 for name in getattr(obj, '__slots__', ()):
2470 if hasattr(self, name):
2471 state[name] = getattr(self, name)
2472
2473 state['_version_id'] = self.current_version_id
2474 try:
2475 del state['__weakref__']
2476 except KeyError:
2477 pass
2478
2479 return state
2480
2482 """
2483 Restore the attributes from a pickled state.
2484 """
2485
2486 del state['_version_id']
2487 for key, value in state.items():
2488 if key not in ('__weakref__',):
2489 setattr(self, key, value)
2490
2493
2495 return not self.__eq__(other)
2496
2499 """
2500 This is info loaded from sconsign.
2501
2502 Attributes unique to FileBuildInfo:
2503 dependency_map : Caches file->csig mapping
2504 for all dependencies. Currently this is only used when using
2505 MD5-timestamp decider.
2506 It's used to ensure that we copy the correct
2507 csig from previous build to be written to .sconsign when current build
2508 is done. Previously the matching of csig to file was strictly by order
2509 they appeared in bdepends, bsources, or bimplicit, and so a change in order
2510 or count of any of these could yield writing wrong csig, and then false positive
2511 rebuilds
2512 """
2513 __slots__ = ['dependency_map', ]
2514 current_version_id = 2
2515
2526
2528 """
2529 Converts this FileBuildInfo object for writing to a .sconsign file
2530
2531 This replaces each Node in our various dependency lists with its
2532 usual string representation: relative to the top-level SConstruct
2533 directory, or an absolute path if it's outside.
2534 """
2535 if os_sep_is_slash:
2536 node_to_str = str
2537 else:
2538 def node_to_str(n):
2539 try:
2540 s = n.get_internal_path()
2541 except AttributeError:
2542 s = str(n)
2543 else:
2544 s = s.replace(OS_SEP, '/')
2545 return s
2546 for attr in ['bsources', 'bdepends', 'bimplicit']:
2547 try:
2548 val = getattr(self, attr)
2549 except AttributeError:
2550 pass
2551 else:
2552 setattr(self, attr, list(map(node_to_str, val)))
2553
2555 """
2556 Converts a newly-read FileBuildInfo object for in-SCons use
2557
2558 For normal up-to-date checking, we don't have any conversion to
2559 perform--but we're leaving this method here to make that clear.
2560 """
2561 pass
2562
2564 """
2565 Prepares a FileBuildInfo object for explaining what changed
2566
2567 The bsources, bdepends and bimplicit lists have all been
2568 stored on disk as paths relative to the top-level SConstruct
2569 directory. Convert the strings to actual Nodes (for use by the
2570 --debug=explain code and --implicit-cache).
2571 """
2572 attrs = [
2573 ('bsources', 'bsourcesigs'),
2574 ('bdepends', 'bdependsigs'),
2575 ('bimplicit', 'bimplicitsigs'),
2576 ]
2577 for (nattr, sattr) in attrs:
2578 try:
2579 strings = getattr(self, nattr)
2580 nodeinfos = getattr(self, sattr)
2581 except AttributeError:
2582 continue
2583 if strings is None or nodeinfos is None:
2584 continue
2585 nodes = []
2586 for s, ni in zip(strings, nodeinfos):
2587 if not isinstance(s, SCons.Node.Node):
2588 s = ni.str_to_node(s)
2589 nodes.append(s)
2590 setattr(self, nattr, nodes)
2591
2603
2604
2605 -class File(Base):
2606 """A class for files in a file system.
2607 """
2608
2609 __slots__ = ['scanner_paths',
2610 'cachedir_csig',
2611 'cachesig',
2612 'repositories',
2613 'srcdir',
2614 'entries',
2615 'searched',
2616 '_sconsign',
2617 'variant_dirs',
2618 'root',
2619 'dirname',
2620 'on_disk_entries',
2621 'released_target_info',
2622 'contentsig']
2623
2624 NodeInfo = FileNodeInfo
2625 BuildInfo = FileBuildInfo
2626
2627 md5_chunksize = 64
2628
2632
2633 - def __init__(self, name, directory, fs):
2637
2638 - def Entry(self, name):
2639 """Create an entry node named 'name' relative to
2640 the directory of this file."""
2641 return self.dir.Entry(name)
2642
2643 - def Dir(self, name, create=True):
2644 """Create a directory node named 'name' relative to
2645 the directory of this file."""
2646 return self.dir.Dir(name, create=create)
2647
2648 - def Dirs(self, pathlist):
2649 """Create a list of directories relative to the SConscript
2650 directory of this file."""
2651 return [self.Dir(p) for p in pathlist]
2652
2653 - def File(self, name):
2654 """Create a file node named 'name' relative to
2655 the directory of this file."""
2656 return self.dir.File(name)
2657
2686
2689
2690 - def get_contents(self):
2692
2694 """
2695 This attempts to figure out what the encoding of the text is
2696 based upon the BOM bytes, and then decodes the contents so that
2697 it's a valid python string.
2698 """
2699 contents = self.get_contents()
2700
2701
2702
2703
2704
2705
2706 if contents[:len(codecs.BOM_UTF8)] == codecs.BOM_UTF8:
2707 return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2708 if contents[:len(codecs.BOM_UTF16_LE)] == codecs.BOM_UTF16_LE:
2709 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2710 if contents[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
2711 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2712 try:
2713 return contents.decode('utf-8')
2714 except UnicodeDecodeError as e:
2715 try:
2716 return contents.decode('latin-1')
2717 except UnicodeDecodeError as e:
2718 return contents.decode('utf-8', error='backslashreplace')
2719
2720
2721 - def get_content_hash(self):
2722 """
2723 Compute and return the MD5 hash for this file.
2724 """
2725 if not self.rexists():
2726 return SCons.Util.MD5signature('')
2727 fname = self.rfile().get_abspath()
2728 try:
2729 cs = SCons.Util.MD5filesignature(fname,
2730 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2731 except EnvironmentError as e:
2732 if not e.filename:
2733 e.filename = fname
2734 raise
2735 return cs
2736
2737 @SCons.Memoize.CountMethodCall
2739 try:
2740 return self._memo['get_size']
2741 except KeyError:
2742 pass
2743
2744 if self.rexists():
2745 size = self.rfile().getsize()
2746 else:
2747 size = 0
2748
2749 self._memo['get_size'] = size
2750
2751 return size
2752
2753 @SCons.Memoize.CountMethodCall
2768
2769 convert_copy_attrs = [
2770 'bsources',
2771 'bimplicit',
2772 'bdepends',
2773 'bact',
2774 'bactsig',
2775 'ninfo',
2776 ]
2777
2778
2779 convert_sig_attrs = [
2780 'bsourcesigs',
2781 'bimplicitsigs',
2782 'bdependsigs',
2783 ]
2784
2785 - def convert_old_entry(self, old_entry):
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853 import SCons.SConsign
2854 new_entry = SCons.SConsign.SConsignEntry()
2855 new_entry.binfo = self.new_binfo()
2856 binfo = new_entry.binfo
2857 for attr in self.convert_copy_attrs:
2858 try:
2859 value = getattr(old_entry, attr)
2860 except AttributeError:
2861 continue
2862 setattr(binfo, attr, value)
2863 delattr(old_entry, attr)
2864 for attr in self.convert_sig_attrs:
2865 try:
2866 sig_list = getattr(old_entry, attr)
2867 except AttributeError:
2868 continue
2869 value = []
2870 for sig in sig_list:
2871 ninfo = self.new_ninfo()
2872 if len(sig) == 32:
2873 ninfo.csig = sig
2874 else:
2875 ninfo.timestamp = sig
2876 value.append(ninfo)
2877 setattr(binfo, attr, value)
2878 delattr(old_entry, attr)
2879 return new_entry
2880
2881 @SCons.Memoize.CountMethodCall
2883 try:
2884 return self._memo['get_stored_info']
2885 except KeyError:
2886 pass
2887
2888 try:
2889 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2890 except (KeyError, EnvironmentError):
2891 import SCons.SConsign
2892 sconsign_entry = SCons.SConsign.SConsignEntry()
2893 sconsign_entry.binfo = self.new_binfo()
2894 sconsign_entry.ninfo = self.new_ninfo()
2895 else:
2896 if isinstance(sconsign_entry, FileBuildInfo):
2897
2898
2899 sconsign_entry = self.convert_old_entry(sconsign_entry)
2900 try:
2901 delattr(sconsign_entry.ninfo, 'bsig')
2902 except AttributeError:
2903 pass
2904
2905 self._memo['get_stored_info'] = sconsign_entry
2906
2907 return sconsign_entry
2908
2914
2917
2919 return (id(env), id(scanner), path)
2920
2921 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2923 """Return the included implicit dependencies in this file.
2924 Cache results so we only scan the file once per path
2925 regardless of how many times this information is requested.
2926 """
2927 memo_key = (id(env), id(scanner), path)
2928 try:
2929 memo_dict = self._memo['get_found_includes']
2930 except KeyError:
2931 memo_dict = {}
2932 self._memo['get_found_includes'] = memo_dict
2933 else:
2934 try:
2935 return memo_dict[memo_key]
2936 except KeyError:
2937 pass
2938
2939 if scanner:
2940 result = [n.disambiguate() for n in scanner(self, env, path)]
2941 else:
2942 result = []
2943
2944 memo_dict[memo_key] = result
2945
2946 return result
2947
2952
2968
2970 """Try to retrieve the node's content from a cache
2971
2972 This method is called from multiple threads in a parallel build,
2973 so only do thread safe stuff here. Do thread unsafe stuff in
2974 built().
2975
2976 Returns true if the node was successfully retrieved.
2977 """
2978 if self.nocache:
2979 return None
2980 if not self.is_derived():
2981 return None
2982 return self.get_build_env().get_CacheDir().retrieve(self)
2983
3006
3008 """Called just after this node has been marked
3009 up-to-date or was built completely.
3010
3011 This is where we try to release as many target node infos
3012 as possible for clean builds and update runs, in order
3013 to minimize the overall memory consumption.
3014
3015 We'd like to remove a lot more attributes like self.sources
3016 and self.sources_set, but they might get used
3017 in a next build step. For example, during configuration
3018 the source files for a built E{*}.o file are used to figure out
3019 which linker to use for the resulting Program (gcc vs. g++)!
3020 That's why we check for the 'keep_targetinfo' attribute,
3021 config Nodes and the Interactive mode just don't allow
3022 an early release of most variables.
3023
3024 In the same manner, we can't simply remove the self.attributes
3025 here. The smart linking relies on the shared flag, and some
3026 parts of the java Tool use it to transport information
3027 about nodes...
3028
3029 @see: built() and Node.release_target_info()
3030 """
3031 if (self.released_target_info or SCons.Node.interactive):
3032 return
3033
3034 if not hasattr(self.attributes, 'keep_targetinfo'):
3035
3036
3037 self.changed(allowcache=True)
3038 self.get_contents_sig()
3039 self.get_build_env()
3040
3041 self.executor = None
3042 self._memo.pop('rfile', None)
3043 self.prerequisites = None
3044
3045 if not len(self.ignore_set):
3046 self.ignore_set = None
3047 if not len(self.implicit_set):
3048 self.implicit_set = None
3049 if not len(self.depends_set):
3050 self.depends_set = None
3051 if not len(self.ignore):
3052 self.ignore = None
3053 if not len(self.depends):
3054 self.depends = None
3055
3056
3057 self.released_target_info = True
3058
3060 if self.rexists():
3061 return None
3062 scb = self.dir.src_builder()
3063 if scb is _null:
3064 scb = None
3065 if scb is not None:
3066 try:
3067 b = self.builder
3068 except AttributeError:
3069 b = None
3070 if b is None:
3071 self.builder_set(scb)
3072 return scb
3073
3075 """Return whether this Node has a source builder or not.
3076
3077 If this Node doesn't have an explicit source code builder, this
3078 is where we figure out, on the fly, if there's a transparent
3079 source code builder for it.
3080
3081 Note that if we found a source builder, we also set the
3082 self.builder attribute, so that all of the methods that actually
3083 *build* this file don't have to do anything different.
3084 """
3085 try:
3086 scb = self.sbuilder
3087 except AttributeError:
3088 scb = self.sbuilder = self.find_src_builder()
3089 return scb is not None
3090
3097
3105
3106
3107
3108
3109
3113
3127
3128
3129
3130
3131
3138
3153
3154 @SCons.Memoize.CountMethodCall
3163
3164
3165
3166
3167
3169 """
3170 Returns the content signature currently stored for this node
3171 if it's been unmodified longer than the max_drift value, or the
3172 max_drift value is 0. Returns None otherwise.
3173 """
3174 old = self.get_stored_info()
3175 mtime = self.get_timestamp()
3176
3177 max_drift = self.fs.max_drift
3178 if max_drift > 0:
3179 if (time.time() - mtime) > max_drift:
3180 try:
3181 n = old.ninfo
3182 if n.timestamp and n.csig and n.timestamp == mtime:
3183 return n.csig
3184 except AttributeError:
3185 pass
3186 elif max_drift == 0:
3187 try:
3188 return old.ninfo.csig
3189 except AttributeError:
3190 pass
3191
3192 return None
3193
3230
3231
3232
3233
3234
3238
3262
3263 - def changed(self, node=None, allowcache=False):
3264 """
3265 Returns if the node is up-to-date with respect to the BuildInfo
3266 stored last time it was built.
3267
3268 For File nodes this is basically a wrapper around Node.changed(),
3269 but we allow the return value to get cached after the reference
3270 to the Executor got released in release_target_info().
3271
3272 @see: Node.changed()
3273 """
3274 if node is None:
3275 try:
3276 return self._memo['changed']
3277 except KeyError:
3278 pass
3279
3280 has_changed = SCons.Node.Node.changed(self, node)
3281 if allowcache:
3282 self._memo['changed'] = has_changed
3283 return has_changed
3284
3285 - def changed_content(self, target, prev_ni, repo_node=None):
3286 cur_csig = self.get_csig()
3287 try:
3288 return cur_csig != prev_ni.csig
3289 except AttributeError:
3290 return 1
3291
3294
3295
3296
3297 __dmap_cache = {}
3298 __dmap_sig_cache = {}
3299
3300
3323
3324
3326 """
3327 In the case comparing node objects isn't sufficient, we'll add the strings for the nodes to the dependency map
3328 :return:
3329 """
3330
3331 first_string = str(next(iter(dmap)))
3332
3333
3334 if first_string not in dmap:
3335 string_dict = {str(child): signature for child, signature in dmap.items()}
3336 dmap.update(string_dict)
3337 return dmap
3338
3340 """
3341 Return a list of corresponding csigs from previous
3342 build in order of the node/files in children.
3343
3344 Args:
3345 self - self
3346 dmap - Dictionary of file -> csig
3347
3348 Returns:
3349 List of csigs for provided list of children
3350 """
3351 prev = []
3352
3353
3354 if len(dmap) == 0:
3355 if MD5_TIMESTAMP_DEBUG: print("Nothing dmap shortcutting")
3356 return None
3357 elif MD5_TIMESTAMP_DEBUG: print("len(dmap):%d"%len(dmap))
3358
3359
3360
3361 if MD5_TIMESTAMP_DEBUG: print("Checking if self is in map:%s id:%s type:%s"%(str(self), id(self), type(self)))
3362 df = dmap.get(self, False)
3363 if df:
3364 return df
3365
3366
3367 rf = self.rfile()
3368 if MD5_TIMESTAMP_DEBUG: print("Checking if self.rfile is in map:%s id:%s type:%s"%(str(rf), id(rf), type(rf)))
3369 rfm = dmap.get(rf, False)
3370 if rfm:
3371 return rfm
3372
3373
3374 c_strs = [str(self)]
3375
3376 if os.altsep:
3377 c_strs.append(c_strs[0].replace(os.sep, os.altsep))
3378
3379
3380
3381 for s in c_strs:
3382 if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map :%s" % s)
3383 df = dmap.get(s, False)
3384 if df:
3385 return df
3386
3387
3388
3389
3390
3391
3392 dmap = self._add_strings_to_dependency_map(dmap)
3393
3394
3395
3396 for s in c_strs:
3397 if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map (now with strings) :%s" % s)
3398 df = dmap.get(s, False)
3399 if df:
3400 return df
3401
3402
3403 if not df:
3404 try:
3405
3406 c_str = self.get_path()
3407 if os.altsep:
3408 c_str = c_str.replace(os.sep, os.altsep)
3409
3410 if MD5_TIMESTAMP_DEBUG: print("Checking if self.get_path is in map (now with strings) :%s" % s)
3411
3412 df = dmap.get(c_str, None)
3413
3414 except AttributeError as e:
3415 raise FileBuildInfoFileToCsigMappingError("No mapping from file name to content signature for :%s"%c_str)
3416
3417 return df
3418
3419 - def changed_timestamp_then_content(self, target, prev_ni, node=None):
3420 """
3421 Used when decider for file is Timestamp-MD5
3422
3423 NOTE: If the timestamp hasn't changed this will skip md5'ing the
3424 file and just copy the prev_ni provided. If the prev_ni
3425 is wrong. It will propagate it.
3426 See: https://github.com/SCons/scons/issues/2980
3427
3428 Args:
3429 self - dependency
3430 target - target
3431 prev_ni - The NodeInfo object loaded from previous builds .sconsign
3432 node - Node instance. Check this node for file existence/timestamp
3433 if specified.
3434
3435 Returns:
3436 Boolean - Indicates if node(File) has changed.
3437 """
3438
3439
3440 bi = node.get_stored_info().binfo
3441 rebuilt = False
3442 try:
3443 dependency_map = bi.dependency_map
3444 except AttributeError as e:
3445 dependency_map = self._build_dependency_map(bi)
3446 rebuilt = True
3447
3448 if len(dependency_map) == 0:
3449
3450
3451
3452 if MD5_TIMESTAMP_DEBUG: print("Skipping checks len(dmap)=0")
3453
3454
3455
3456 self.get_csig()
3457 return True
3458
3459 new_prev_ni = self._get_previous_signatures(dependency_map)
3460 new = self.changed_timestamp_match(target, new_prev_ni)
3461
3462 if MD5_TIMESTAMP_DEBUG:
3463 old = self.changed_timestamp_match(target, prev_ni)
3464
3465 if old != new:
3466 print("Mismatch self.changed_timestamp_match(%s, prev_ni) old:%s new:%s"%(str(target), old, new))
3467 new_prev_ni = self._get_previous_signatures(dependency_map)
3468
3469 if not new:
3470 try:
3471
3472 self.get_ninfo().csig = new_prev_ni.csig
3473 except AttributeError:
3474 pass
3475 return False
3476 return self.changed_content(target, new_prev_ni)
3477
3483
3485 """
3486 Return True if the timestamps don't match or if there is no previous timestamp
3487 :param target:
3488 :param prev_ni: Information about the node from the previous build
3489 :return:
3490 """
3491 try:
3492 return self.get_timestamp() != prev_ni.timestamp
3493 except AttributeError:
3494 return 1
3495
3497 """Check for whether the Node is current
3498 In all cases self is the target we're checking to see if it's up to date
3499 """
3500
3501 T = 0
3502 if T: Trace('is_up_to_date(%s):' % self)
3503 if not self.exists():
3504 if T: Trace(' not self.exists():')
3505
3506 r = self.rfile()
3507 if r != self:
3508
3509 if not self.changed(r):
3510 if T: Trace(' changed(%s):' % r)
3511
3512 if self._local:
3513
3514 e = LocalCopy(self, r, None)
3515 if isinstance(e, SCons.Errors.BuildError):
3516
3517
3518 raise e
3519 SCons.Node.store_info_map[self.store_info](self)
3520 if T: Trace(' 1\n')
3521 return 1
3522 self.changed()
3523 if T: Trace(' None\n')
3524 return None
3525 else:
3526 r = self.changed()
3527 if T: Trace(' self.exists(): %s\n' % r)
3528 return not r
3529
3530 @SCons.Memoize.CountMethodCall
3565
3567 """
3568 For this node, find if there exists a corresponding file in one or more repositories
3569 :return: list of corresponding files in repositories
3570 """
3571 retvals = []
3572
3573 norm_name = _my_normcase(self.name)
3574 for repo_dir in self.dir.get_all_rdirs():
3575 try:
3576 node = repo_dir.entries[norm_name]
3577 except KeyError:
3578 node = repo_dir.file_on_disk(self.name)
3579
3580 if node and node.exists() and \
3581 (isinstance(node, File) or isinstance(node, Entry) \
3582 or not node.is_derived()):
3583 retvals.append(node)
3584
3585 return retvals
3586
3587
3589 return str(self.rfile())
3590
3592 """
3593 Fetch a Node's content signature for purposes of computing
3594 another Node's cachesig.
3595
3596 This is a wrapper around the normal get_csig() method that handles
3597 the somewhat obscure case of using CacheDir with the -n option.
3598 Any files that don't exist would normally be "built" by fetching
3599 them from the cache, but the normal get_csig() method will try
3600 to open up the local file, which doesn't exist because the -n
3601 option meant we didn't actually pull the file from cachedir.
3602 But since the file *does* actually exist in the cachedir, we
3603 can use its contents for the csig.
3604 """
3605 try:
3606 return self.cachedir_csig
3607 except AttributeError:
3608 pass
3609
3610 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
3611 if not self.exists() and cachefile and os.path.exists(cachefile):
3612 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
3613 SCons.Node.FS.File.md5_chunksize * 1024)
3614 else:
3615 self.cachedir_csig = self.get_csig()
3616 return self.cachedir_csig
3617
3618 - def get_contents_sig(self):
3619 """
3620 A helper method for get_cachedir_bsig.
3621
3622 It computes and returns the signature for this
3623 node's contents.
3624 """
3625
3626 try:
3627 return self.contentsig
3628 except AttributeError:
3629 pass
3630
3631 executor = self.get_executor()
3632
3633 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
3634 return result
3635
3637 """
3638 Return the signature for a cached file, including
3639 its children.
3640
3641 It adds the path of the cached file to the cache signature,
3642 because multiple targets built by the same action will all
3643 have the same build signature, and we have to differentiate
3644 them somehow.
3645
3646 Signature should normally be string of hex digits.
3647 """
3648 try:
3649 return self.cachesig
3650 except AttributeError:
3651 pass
3652
3653
3654 children = self.children()
3655 sigs = [n.get_cachedir_csig() for n in children]
3656
3657
3658 sigs.append(self.get_contents_sig())
3659
3660
3661 sigs.append(self.get_internal_path())
3662
3663
3664 result = self.cachesig = SCons.Util.MD5collect(sigs)
3665 return result
3666
3667 default_fs = None
3674
3676 """
3677 """
3678
3681
3683 """
3684 A helper method for find_file() that looks up a directory for
3685 a file we're trying to find. This only creates the Dir Node if
3686 it exists on-disk, since if the directory doesn't exist we know
3687 we won't find any files in it... :-)
3688
3689 It would be more compact to just use this as a nested function
3690 with a default keyword argument (see the commented-out version
3691 below), but that doesn't work unless you have nested scopes,
3692 so we define it here just so this work under Python 1.5.2.
3693 """
3694 if fd is None:
3695 fd = self.default_filedir
3696 dir, name = os.path.split(fd)
3697 drive, d = _my_splitdrive(dir)
3698 if not name and d[:1] in ('/', OS_SEP):
3699
3700 return p.fs.get_root(drive)
3701 if dir:
3702 p = self.filedir_lookup(p, dir)
3703 if not p:
3704 return None
3705 norm_name = _my_normcase(name)
3706 try:
3707 node = p.entries[norm_name]
3708 except KeyError:
3709 return p.dir_on_disk(name)
3710 if isinstance(node, Dir):
3711 return node
3712 if isinstance(node, Entry):
3713 node.must_be_same(Dir)
3714 return node
3715 return None
3716
3718 return (filename, paths)
3719
3720 @SCons.Memoize.CountDictCall(_find_file_key)
3721 - def find_file(self, filename, paths, verbose=None):
3722 """
3723 Find a node corresponding to either a derived file or a file that exists already.
3724
3725 Only the first file found is returned, and none is returned if no file is found.
3726
3727 filename: A filename to find
3728 paths: A list of directory path *nodes* to search in. Can be represented as a list, a tuple, or a callable that is called with no arguments and returns the list or tuple.
3729
3730 returns The node created from the found file.
3731
3732 """
3733 memo_key = self._find_file_key(filename, paths)
3734 try:
3735 memo_dict = self._memo['find_file']
3736 except KeyError:
3737 memo_dict = {}
3738 self._memo['find_file'] = memo_dict
3739 else:
3740 try:
3741 return memo_dict[memo_key]
3742 except KeyError:
3743 pass
3744
3745 if verbose and not callable(verbose):
3746 if not SCons.Util.is_String(verbose):
3747 verbose = "find_file"
3748 _verbose = u' %s: ' % verbose
3749 verbose = lambda s: sys.stdout.write(_verbose + s)
3750
3751 filedir, filename = os.path.split(filename)
3752 if filedir:
3753 self.default_filedir = filedir
3754 paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3755
3756 result = None
3757 for dir in paths:
3758 if verbose:
3759 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3760 node, d = dir.srcdir_find_file(filename)
3761 if node:
3762 if verbose:
3763 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3764 result = node
3765 break
3766
3767 memo_dict[memo_key] = result
3768
3769 return result
3770
3771 find_file = FileFinder().find_file
3775 """
3776 Invalidate the memoized values of all Nodes (files or directories)
3777 that are associated with the given entries. Has been added to
3778 clear the cache of nodes affected by a direct execution of an
3779 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3780 inconsistent if the action is run through Execute(). The argument
3781 `targets` can be a single Node object or filename, or a sequence
3782 of Nodes/filenames.
3783 """
3784 from traceback import extract_stack
3785
3786
3787
3788
3789
3790
3791 for f in extract_stack():
3792 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3793 break
3794 else:
3795
3796 return
3797
3798 if not SCons.Util.is_List(targets):
3799 targets = [targets]
3800
3801 for entry in targets:
3802
3803
3804 try:
3805 entry.clear_memoized_values()
3806 except AttributeError:
3807
3808
3809
3810 node = get_default_fs().Entry(entry)
3811 if node:
3812 node.clear_memoized_values()
3813
3814
3815
3816
3817
3818
3819