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
35 __revision__ = "src/engine/SCons/Node/FS.py rel_2.4.0:3365:9259ea1c13d7 2015/09/21 14:03:43 bdbaddog"
36
37 import fnmatch
38 import os
39 import re
40 import shutil
41 import stat
42 import sys
43 import time
44 import codecs
45
46 import SCons.Action
47 import SCons.Debug
48 from SCons.Debug import logInstanceCreation
49 import SCons.Errors
50 import SCons.Memoize
51 import SCons.Node
52 import SCons.Node.Alias
53 import SCons.Subst
54 import SCons.Util
55 import SCons.Warnings
56
57 from SCons.Debug import Trace
58
59 print_duplicate = 0
63 raise NotImplementedError
64
72
73 _sconsign_map = {0 : sconsign_none,
74 1 : sconsign_dir}
75
76 -class EntryProxyAttributeError(AttributeError):
77 """
78 An AttributeError subclass for recording and displaying the name
79 of the underlying Entry involved in an AttributeError exception.
80 """
81 - def __init__(self, entry_proxy, attribute):
82 AttributeError.__init__(self)
83 self.entry_proxy = entry_proxy
84 self.attribute = attribute
86 entry = self.entry_proxy.get()
87 fmt = "%s instance %s has no attribute %s"
88 return fmt % (entry.__class__.__name__,
89 repr(entry.name),
90 repr(self.attribute))
91
92
93
94 default_max_drift = 2*24*60*60
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 Save_Strings = None
119
120
121
122
123
124
125
126
127 do_splitdrive = None
128 _my_splitdrive =None
148 else:
149 def splitdrive(p):
150 if p[1:2] == ':':
151 return p[:2], p[2:]
152 return '', p
153 _my_splitdrive = splitdrive
154
155
156
157 global OS_SEP
158 global UNC_PREFIX
159 global os_sep_is_slash
160
161 OS_SEP = os.sep
162 UNC_PREFIX = OS_SEP + OS_SEP
163 os_sep_is_slash = OS_SEP == '/'
164
165 initialize_do_splitdrive()
166
167
168 needs_normpath_check = re.compile(
169 r'''
170 # We need to renormalize the path if it contains any consecutive
171 # '/' characters.
172 .*// |
173
174 # We need to renormalize the path if it contains a '..' directory.
175 # Note that we check for all the following cases:
176 #
177 # a) The path is a single '..'
178 # b) The path starts with '..'. E.g. '../' or '../moredirs'
179 # but we not match '..abc/'.
180 # c) The path ends with '..'. E.g. '/..' or 'dirs/..'
181 # d) The path contains a '..' in the middle.
182 # E.g. dirs/../moredirs
183
184 (.*/)?\.\.(?:/|$) |
185
186 # We need to renormalize the path if it contains a '.'
187 # directory, but NOT if it is a single '.' '/' characters. We
188 # do not want to match a single '.' because this case is checked
189 # for explicitely since this is common enough case.
190 #
191 # Note that we check for all the following cases:
192 #
193 # a) We don't match a single '.'
194 # b) We match if the path starts with '.'. E.g. './' or
195 # './moredirs' but we not match '.abc/'.
196 # c) We match if the path ends with '.'. E.g. '/.' or
197 # 'dirs/.'
198 # d) We match if the path contains a '.' in the middle.
199 # E.g. dirs/./moredirs
200
201 \./|.*/\.(?:/|$)
202
203 ''',
204 re.VERBOSE
205 )
206 needs_normpath_match = needs_normpath_check.match
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222 if hasattr(os, 'link'):
235 else:
236 _hardlink_func = None
237
238 if hasattr(os, 'symlink'):
241 else:
242 _softlink_func = None
248
249
250 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
251 'hard-copy', 'soft-copy', 'copy']
252
253 Link_Funcs = []
277
309
310 Link = SCons.Action.Action(LinkFunc, None)
312 return 'Local copy of %s from %s' % (target[0], source[0])
313
314 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
320
321 Unlink = SCons.Action.Action(UnlinkFunc, None)
328
329 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
330
331 MkdirBuilder = None
347
350
351 _null = _Null()
352
353 DefaultSCCSBuilder = None
354 DefaultRCSBuilder = None
367
379
380
381 _is_cygwin = sys.platform == "cygwin"
382 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
385 else:
388
393 self.type = type
394 self.do = do
395 self.ignore = ignore
396 self.func = do
398 return self.func(*args, **kw)
399 - def set(self, list):
400 if self.type in list:
401 self.func = self.do
402 else:
403 self.func = self.ignore
404
406 result = predicate()
407 try:
408
409
410
411
412
413
414 if node._memo['stat'] is None:
415 del node._memo['stat']
416 except (AttributeError, KeyError):
417 pass
418 if result:
419 raise TypeError(errorfmt % node.get_abspath())
420
423
436
439
452
455
456 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
457 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
458 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
459
460 diskcheckers = [
461 diskcheck_match,
462 diskcheck_rcs,
463 diskcheck_sccs,
464 ]
469
472
473
474
475 -class EntryProxy(SCons.Util.Proxy):
476
477 __str__ = SCons.Util.Delegate('__str__')
478
479 - def __get_abspath(self):
480 entry = self.get()
481 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
482 entry.name + "_abspath")
483
484 - def __get_filebase(self):
488
489 - def __get_suffix(self):
493
494 - def __get_file(self):
497
498 - def __get_base_path(self):
499 """Return the file's directory and file name, with the
500 suffix stripped."""
501 entry = self.get()
502 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
503 entry.name + "_base")
504
506 """Return the path with / as the path separator,
507 regardless of platform."""
508 if os_sep_is_slash:
509 return self
510 else:
511 entry = self.get()
512 r = entry.get_path().replace(OS_SEP, '/')
513 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
514
516 """Return the path with \ as the path separator,
517 regardless of platform."""
518 if OS_SEP == '\\':
519 return self
520 else:
521 entry = self.get()
522 r = entry.get_path().replace(OS_SEP, '\\')
523 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
524
525 - def __get_srcnode(self):
526 return EntryProxy(self.get().srcnode())
527
528 - def __get_srcdir(self):
529 """Returns the directory containing the source node linked to this
530 node via VariantDir(), or the directory of this node if not linked."""
531 return EntryProxy(self.get().srcnode().dir)
532
533 - def __get_rsrcnode(self):
534 return EntryProxy(self.get().srcnode().rfile())
535
536 - def __get_rsrcdir(self):
537 """Returns the directory containing the source node linked to this
538 node via VariantDir(), or the directory of this node if not linked."""
539 return EntryProxy(self.get().srcnode().rfile().dir)
540
541 - def __get_dir(self):
542 return EntryProxy(self.get().dir)
543
544 dictSpecialAttrs = { "base" : __get_base_path,
545 "posix" : __get_posix_path,
546 "windows" : __get_windows_path,
547 "win32" : __get_windows_path,
548 "srcpath" : __get_srcnode,
549 "srcdir" : __get_srcdir,
550 "dir" : __get_dir,
551 "abspath" : __get_abspath,
552 "filebase" : __get_filebase,
553 "suffix" : __get_suffix,
554 "file" : __get_file,
555 "rsrcpath" : __get_rsrcnode,
556 "rsrcdir" : __get_rsrcdir,
557 }
558
559 - def __getattr__(self, name):
560
561
562 try:
563 attr_function = self.dictSpecialAttrs[name]
564 except KeyError:
565 try:
566 attr = SCons.Util.Proxy.__getattr__(self, name)
567 except AttributeError, e:
568
569
570
571 raise EntryProxyAttributeError(self, name)
572 return attr
573 else:
574 return attr_function(self)
575
576 -class Base(SCons.Node.Node):
577 """A generic class for file system entries. This class is for
578 when we don't know yet whether the entry being looked up is a file
579 or a directory. Instances of this class can morph into either
580 Dir or File objects by a later, more precise lookup.
581
582 Note: this class does not define __cmp__ and __hash__ for
583 efficiency reasons. SCons does a lot of comparing of
584 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
585 as fast as possible, which means we want to use Python's built-in
586 object identity comparisons.
587 """
588
589 __slots__ = ['name',
590 'fs',
591 '_abspath',
592 '_labspath',
593 '_path',
594 '_tpath',
595 '_path_elements',
596 'dir',
597 'cwd',
598 'duplicate',
599 '_local',
600 'sbuilder',
601 '_proxy',
602 '_func_sconsign']
603
604 - def __init__(self, name, directory, fs):
640
642 return '"' + self.__str__() + '"'
643
645 """
646 This node, which already existed, is being looked up as the
647 specified klass. Raise an exception if it isn't.
648 """
649 if isinstance(self, klass) or klass is Entry:
650 return
651 raise TypeError("Tried to lookup %s '%s' as a %s." %\
652 (self.__class__.__name__, self.get_internal_path(), klass.__name__))
653
656
659
662
664 """ Together with the node_bwcomp dict defined below,
665 this method provides a simple backward compatibility
666 layer for the Node attributes 'abspath', 'labspath',
667 'path', 'tpath', 'suffix' and 'path_elements'. These Node
668 attributes used to be directly available in v2.3 and earlier, but
669 have been replaced by getter methods that initialize the
670 single variables lazily when required, in order to save memory.
671 The redirection to the getters lets older Tools and
672 SConstruct continue to work without any additional changes,
673 fully transparent to the user.
674 Note, that __getattr__ is only called as fallback when the
675 requested attribute can't be found, so there should be no
676 speed performance penalty involved for standard builds.
677 """
678 if attr in node_bwcomp:
679 return node_bwcomp[attr](self)
680
681 raise AttributeError("%r object has no attribute %r" %
682 (self.__class__, attr))
683
691
692 @SCons.Memoize.CountMethodCall
701
726
727 rstr = __str__
728
729 @SCons.Memoize.CountMethodCall
737
740
743
745 st = self.stat()
746 if st: return st[stat.ST_MTIME]
747 else: return None
748
750 st = self.stat()
751 if st: return st[stat.ST_SIZE]
752 else: return None
753
755 st = self.stat()
756 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
757
759 st = self.stat()
760 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
761
762 if hasattr(os, 'symlink'):
767 else:
770
776
779
791
793 """Return path relative to the current working directory of the
794 Node.FS.Base object that owns us."""
795 if not dir:
796 dir = self.fs.getcwd()
797 if self == dir:
798 return '.'
799 path_elems = self.get_path_elements()
800 pathname = ''
801 try: i = path_elems.index(dir)
802 except ValueError:
803 for p in path_elems[:-1]:
804 pathname += p.dirname
805 else:
806 for p in path_elems[i+1:-1]:
807 pathname += p.dirname
808 return pathname + path_elems[-1].name
809
815
817 """Fetch the source code builder for this node.
818
819 If there isn't one, we cache the source code builder specified
820 for the directory (which in turn will cache the value from its
821 parent directory, and so on up to the file system root).
822 """
823 try:
824 scb = self.sbuilder
825 except AttributeError:
826 scb = self.dir.src_builder()
827 self.sbuilder = scb
828 return scb
829
833
837
843
849
852
854
855
856
857 return self.name
858
860 try:
861 return self._proxy
862 except AttributeError:
863 ret = EntryProxy(self)
864 self._proxy = ret
865 return ret
866
868 """
869
870 Generates a target entry that corresponds to this entry (usually
871 a source file) with the specified prefix and suffix.
872
873 Note that this method can be overridden dynamically for generated
874 files that need different behavior. See Tool/swig.py for
875 an example.
876 """
877 return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext)
878
881
882 @SCons.Memoize.CountDictCall(_Rfindalldirs_key)
884 """
885 Return all of the directories for a given path list, including
886 corresponding "backing" directories in any repositories.
887
888 The Node lookups are relative to this Node (typically a
889 directory), so memoizing result saves cycles from looking
890 up the same path for each target in a given directory.
891 """
892 try:
893 memo_dict = self._memo['Rfindalldirs']
894 except KeyError:
895 memo_dict = {}
896 self._memo['Rfindalldirs'] = memo_dict
897 else:
898 try:
899 return memo_dict[pathlist]
900 except KeyError:
901 pass
902
903 create_dir_relative_to_self = self.Dir
904 result = []
905 for path in pathlist:
906 if isinstance(path, SCons.Node.Node):
907 result.append(path)
908 else:
909 dir = create_dir_relative_to_self(path)
910 result.extend(dir.get_all_rdirs())
911
912 memo_dict[pathlist] = result
913
914 return result
915
916 - def RDirs(self, pathlist):
917 """Search for a list of directories in the Repository list."""
918 cwd = self.cwd or self.fs._cwd
919 return cwd.Rfindalldirs(pathlist)
920
921 @SCons.Memoize.CountMethodCall
923 try:
924 return self._memo['rentry']
925 except KeyError:
926 pass
927 result = self
928 if not self.exists():
929 norm_name = _my_normcase(self.name)
930 for dir in self.dir.get_all_rdirs():
931 try:
932 node = dir.entries[norm_name]
933 except KeyError:
934 if dir.entry_exists_on_disk(self.name):
935 result = dir.Entry(self.name)
936 break
937 self._memo['rentry'] = result
938 return result
939
940 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
942
943
944
945
946
947 node_bwcomp = {'abspath' : Base.get_abspath,
948 'labspath' : Base.get_labspath,
949 'path' : Base.get_internal_path,
950 'tpath' : Base.get_tpath,
951 'path_elements' : Base.get_path_elements,
952 'suffix' : Base.get_suffix}
953
954 -class Entry(Base):
955 """This is the class for generic Node.FS entries--that is, things
956 that could be a File or a Dir, but we're just not sure yet.
957 Consequently, the methods in this class really exist just to
958 transform their associated object into the right class when the
959 time comes, and then call the same-named method in the transformed
960 class."""
961
962 __slots__ = ['scanner_paths',
963 'cachedir_csig',
964 'cachesig',
965 'repositories',
966 'srcdir',
967 'entries',
968 'searched',
969 '_sconsign',
970 'variant_dirs',
971 'root',
972 'dirname',
973 'on_disk_entries',
974 'sccs_dir',
975 'rcs_dir',
976 'released_target_info',
977 'contentsig']
978
979 - def __init__(self, name, directory, fs):
980 Base.__init__(self, name, directory, fs)
981 self._func_exists = 3
982 self._func_get_contents = 1
983
984 - def diskcheck_match(self):
986
987 - def disambiguate(self, must_exist=None):
988 """
989 """
990 if self.isdir():
991 self.__class__ = Dir
992 self._morph()
993 elif self.isfile():
994 self.__class__ = File
995 self._morph()
996 self.clear()
997 else:
998
999
1000
1001
1002
1003
1004
1005
1006
1007 srcdir = self.dir.srcnode()
1008 if srcdir != self.dir and \
1009 srcdir.entry_exists_on_disk(self.name) and \
1010 self.srcnode().isdir():
1011 self.__class__ = Dir
1012 self._morph()
1013 elif must_exist:
1014 msg = "No such file or directory: '%s'" % self.get_abspath()
1015 raise SCons.Errors.UserError(msg)
1016 else:
1017 self.__class__ = File
1018 self._morph()
1019 self.clear()
1020 return self
1021
1023 """We're a generic Entry, but the caller is actually looking for
1024 a File at this point, so morph into one."""
1025 self.__class__ = File
1026 self._morph()
1027 self.clear()
1028 return File.rfile(self)
1029
1030 - def scanner_key(self):
1031 return self.get_suffix()
1032
1033 - def get_contents(self):
1034 """Fetch the contents of the entry. Returns the exact binary
1035 contents of the file."""
1036 return SCons.Node._get_contents_map[self._func_get_contents](self)
1037
1039 """Fetch the decoded text contents of a Unicode encoded Entry.
1040
1041 Since this should return the text contents from the file
1042 system, we check to see into what sort of subclass we should
1043 morph this Entry."""
1044 try:
1045 self = self.disambiguate(must_exist=1)
1046 except SCons.Errors.UserError:
1047
1048
1049
1050
1051
1052 return ''
1053 else:
1054 return self.get_text_contents()
1055
1056 - def must_be_same(self, klass):
1057 """Called to make sure a Node is a Dir. Since we're an
1058 Entry, we can morph into one."""
1059 if self.__class__ is not klass:
1060 self.__class__ = klass
1061 self._morph()
1062 self.clear()
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1077
1078 - def rel_path(self, other):
1079 d = self.disambiguate()
1080 if d.__class__ is Entry:
1081 raise Exception("rel_path() could not disambiguate File/Dir")
1082 return d.rel_path(other)
1083
1084 - def new_ninfo(self):
1085 return self.disambiguate().new_ninfo()
1086
1087 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1088 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1089
1090 - def get_subst_proxy(self):
1092
1093
1094
1095 _classEntry = Entry
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116 - def chmod(self, path, mode):
1118 - def copy(self, src, dst):
1119 return shutil.copy(src, dst)
1120 - def copy2(self, src, dst):
1121 return shutil.copy2(src, dst)
1132 - def link(self, src, dst):
1133 return os.link(src, dst)
1143 return os.rename(old, new)
1144 - def stat(self, path):
1148 - def open(self, path):
1152
1153 if hasattr(os, 'symlink'):
1156 else:
1159
1160 if hasattr(os, 'readlink'):
1163 else:
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177 -class FS(LocalFS):
1178
1180 """Initialize the Node.FS subsystem.
1181
1182 The supplied path is the top of the source tree, where we
1183 expect to find the top-level build file. If no path is
1184 supplied, the current directory is the default.
1185
1186 The path argument must be a valid absolute path.
1187 """
1188 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS')
1189
1190 self._memo = {}
1191
1192 self.Root = {}
1193 self.SConstruct_dir = None
1194 self.max_drift = default_max_drift
1195
1196 self.Top = None
1197 if path is None:
1198 self.pathTop = os.getcwd()
1199 else:
1200 self.pathTop = path
1201 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1202
1203 self.Top = self.Dir(self.pathTop)
1204 self.Top._path = '.'
1205 self.Top._tpath = '.'
1206 self._cwd = self.Top
1207
1208 DirNodeInfo.fs = self
1209 FileNodeInfo.fs = self
1210
1212 self.SConstruct_dir = dir
1213
1215 return self.max_drift
1216
1218 self.max_drift = max_drift
1219
1221 if hasattr(self, "_cwd"):
1222 return self._cwd
1223 else:
1224 return "<no cwd>"
1225
1226 - def chdir(self, dir, change_os_dir=0):
1227 """Change the current working directory for lookups.
1228 If change_os_dir is true, we will also change the "real" cwd
1229 to match.
1230 """
1231 curr=self._cwd
1232 try:
1233 if dir is not None:
1234 self._cwd = dir
1235 if change_os_dir:
1236 os.chdir(dir.get_abspath())
1237 except OSError:
1238 self._cwd = curr
1239 raise
1240
1242 """
1243 Returns the root directory for the specified drive, creating
1244 it if necessary.
1245 """
1246 drive = _my_normcase(drive)
1247 try:
1248 return self.Root[drive]
1249 except KeyError:
1250 root = RootDir(drive, self)
1251 self.Root[drive] = root
1252 if not drive:
1253 self.Root[self.defaultDrive] = root
1254 elif drive == self.defaultDrive:
1255 self.Root[''] = root
1256 return root
1257
1258 - def _lookup(self, p, directory, fsclass, create=1):
1259 """
1260 The generic entry point for Node lookup with user-supplied data.
1261
1262 This translates arbitrary input into a canonical Node.FS object
1263 of the specified fsclass. The general approach for strings is
1264 to turn it into a fully normalized absolute path and then call
1265 the root directory's lookup_abs() method for the heavy lifting.
1266
1267 If the path name begins with '#', it is unconditionally
1268 interpreted relative to the top-level directory of this FS. '#'
1269 is treated as a synonym for the top-level SConstruct directory,
1270 much like '~' is treated as a synonym for the user's home
1271 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1272 to the 'foo' subdirectory underneath the top-level SConstruct
1273 directory.
1274
1275 If the path name is relative, then the path is looked up relative
1276 to the specified directory, or the current directory (self._cwd,
1277 typically the SConscript directory) if the specified directory
1278 is None.
1279 """
1280 if isinstance(p, Base):
1281
1282
1283 p.must_be_same(fsclass)
1284 return p
1285
1286 p = str(p)
1287
1288 if not os_sep_is_slash:
1289 p = p.replace(OS_SEP, '/')
1290
1291 if p[0:1] == '#':
1292
1293
1294
1295 p = p[1:]
1296 directory = self.Top
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308 if do_splitdrive:
1309 drive, p = _my_splitdrive(p)
1310 if drive:
1311 root = self.get_root(drive)
1312 else:
1313 root = directory.root
1314 else:
1315 root = directory.root
1316
1317
1318
1319 p = p.strip('/')
1320
1321 needs_normpath = needs_normpath_match(p)
1322
1323
1324 if p in ('', '.'):
1325 p = directory.get_labspath()
1326 else:
1327 p = directory.get_labspath() + '/' + p
1328 else:
1329 if do_splitdrive:
1330 drive, p = _my_splitdrive(p)
1331 if drive and not p:
1332
1333
1334
1335 p = '/'
1336 else:
1337 drive = ''
1338
1339
1340
1341 if p != '/':
1342 p = p.rstrip('/')
1343
1344 needs_normpath = needs_normpath_match(p)
1345
1346 if p[0:1] == '/':
1347
1348 root = self.get_root(drive)
1349 else:
1350
1351
1352
1353
1354 if directory:
1355 if not isinstance(directory, Dir):
1356 directory = self.Dir(directory)
1357 else:
1358 directory = self._cwd
1359
1360 if p in ('', '.'):
1361 p = directory.get_labspath()
1362 else:
1363 p = directory.get_labspath() + '/' + p
1364
1365 if drive:
1366 root = self.get_root(drive)
1367 else:
1368 root = directory.root
1369
1370 if needs_normpath is not None:
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380 ins = p.split('/')[1:]
1381 outs = []
1382 for d in ins:
1383 if d == '..':
1384 try:
1385 outs.pop()
1386 except IndexError:
1387 pass
1388 elif d not in ('', '.'):
1389 outs.append(d)
1390 p = '/' + '/'.join(outs)
1391
1392 return root._lookup_abs(p, fsclass, create)
1393
1394 - def Entry(self, name, directory = None, create = 1):
1395 """Look up or create a generic Entry node with the specified name.
1396 If the name is a relative path (begins with ./, ../, or a file
1397 name), then it is looked up relative to the supplied directory
1398 node, or to the top level directory of the FS (supplied at
1399 construction time) if no directory is supplied.
1400 """
1401 return self._lookup(name, directory, Entry, create)
1402
1403 - def File(self, name, directory = None, create = 1):
1404 """Look up or create a File node with the specified name. If
1405 the name is a relative path (begins with ./, ../, or a file name),
1406 then it is looked up relative to the supplied directory node,
1407 or to the top level directory of the FS (supplied at construction
1408 time) if no directory is supplied.
1409
1410 This method will raise TypeError if a directory is found at the
1411 specified path.
1412 """
1413 return self._lookup(name, directory, File, create)
1414
1415 - def Dir(self, name, directory = None, create = True):
1416 """Look up or create a Dir node with the specified name. If
1417 the name is a relative path (begins with ./, ../, or a file name),
1418 then it is looked up relative to the supplied directory node,
1419 or to the top level directory of the FS (supplied at construction
1420 time) if no directory is supplied.
1421
1422 This method will raise TypeError if a normal file is found at the
1423 specified path.
1424 """
1425 return self._lookup(name, directory, Dir, create)
1426
1427 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1428 """Link the supplied variant directory to the source directory
1429 for purposes of building files."""
1430
1431 if not isinstance(src_dir, SCons.Node.Node):
1432 src_dir = self.Dir(src_dir)
1433 if not isinstance(variant_dir, SCons.Node.Node):
1434 variant_dir = self.Dir(variant_dir)
1435 if src_dir.is_under(variant_dir):
1436 raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
1437 if variant_dir.srcdir:
1438 if variant_dir.srcdir == src_dir:
1439 return
1440 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
1441 variant_dir.link(src_dir, duplicate)
1442
1449
1451 """Create targets in corresponding variant directories
1452
1453 Climb the directory tree, and look up path names
1454 relative to any linked variant directories we find.
1455
1456 Even though this loops and walks up the tree, we don't memoize
1457 the return value because this is really only used to process
1458 the command-line targets.
1459 """
1460 targets = []
1461 message = None
1462 fmt = "building associated VariantDir targets: %s"
1463 start_dir = dir
1464 while dir:
1465 for bd in dir.variant_dirs:
1466 if start_dir.is_under(bd):
1467
1468 return [orig], fmt % str(orig)
1469 p = os.path.join(bd._path, *tail)
1470 targets.append(self.Entry(p))
1471 tail = [dir.name] + tail
1472 dir = dir.up()
1473 if targets:
1474 message = fmt % ' '.join(map(str, targets))
1475 return targets, message
1476
1477 - def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
1478 """
1479 Globs
1480
1481 This is mainly a shim layer
1482 """
1483 if cwd is None:
1484 cwd = self.getcwd()
1485 return cwd.glob(pathname, ondisk, source, strings, exclude)
1486
1504
1508
1509 glob_magic_check = re.compile('[*?[]')
1513
1515 """A class for directories in a file system.
1516 """
1517
1518 __slots__ = ['scanner_paths',
1519 'cachedir_csig',
1520 'cachesig',
1521 'repositories',
1522 'srcdir',
1523 'entries',
1524 'searched',
1525 '_sconsign',
1526 'variant_dirs',
1527 'root',
1528 'dirname',
1529 'on_disk_entries',
1530 'sccs_dir',
1531 'rcs_dir',
1532 'released_target_info',
1533 'contentsig']
1534
1535 NodeInfo = DirNodeInfo
1536 BuildInfo = DirBuildInfo
1537
1538 - def __init__(self, name, directory, fs):
1542
1608
1612
1614 """Called when we change the repository(ies) for a directory.
1615 This clears any cached information that is invalidated by changing
1616 the repository."""
1617
1618 for node in self.entries.values():
1619 if node != self.dir:
1620 if node != self and isinstance(node, Dir):
1621 node.__clearRepositoryCache(duplicate)
1622 else:
1623 node.clear()
1624 try:
1625 del node._srcreps
1626 except AttributeError:
1627 pass
1628 if duplicate is not None:
1629 node.duplicate=duplicate
1630
1634
1635 - def Entry(self, name):
1636 """
1637 Looks up or creates an entry node named 'name' relative to
1638 this directory.
1639 """
1640 return self.fs.Entry(name, self)
1641
1642 - def Dir(self, name, create=True):
1643 """
1644 Looks up or creates a directory node named 'name' relative to
1645 this directory.
1646 """
1647 return self.fs.Dir(name, self, create)
1648
1649 - def File(self, name):
1650 """
1651 Looks up or creates a file node named 'name' relative to
1652 this directory.
1653 """
1654 return self.fs.File(name, self)
1655
1656 - def link(self, srcdir, duplicate):
1663
1670
1671 @SCons.Memoize.CountMethodCall
1693
1699
1702
1705
1706 @SCons.Memoize.CountDictCall(_rel_path_key)
1708 """Return a path to "other" relative to this directory.
1709 """
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721 try:
1722 memo_dict = self._memo['rel_path']
1723 except KeyError:
1724 memo_dict = {}
1725 self._memo['rel_path'] = memo_dict
1726 else:
1727 try:
1728 return memo_dict[other]
1729 except KeyError:
1730 pass
1731
1732 if self is other:
1733 result = '.'
1734
1735 elif not other in self._path_elements:
1736 try:
1737 other_dir = other.get_dir()
1738 except AttributeError:
1739 result = str(other)
1740 else:
1741 if other_dir is None:
1742 result = other.name
1743 else:
1744 dir_rel_path = self.rel_path(other_dir)
1745 if dir_rel_path == '.':
1746 result = other.name
1747 else:
1748 result = dir_rel_path + OS_SEP + other.name
1749 else:
1750 i = self._path_elements.index(other) + 1
1751
1752 path_elems = ['..'] * (len(self._path_elements) - i) \
1753 + [n.name for n in other._path_elements[i:]]
1754
1755 result = OS_SEP.join(path_elems)
1756
1757 memo_dict[other] = result
1758
1759 return result
1760
1764
1768
1770 """Return this directory's implicit dependencies.
1771
1772 We don't bother caching the results because the scan typically
1773 shouldn't be requested more than once (as opposed to scanning
1774 .h file contents, which can be requested as many times as the
1775 files is #included by other files).
1776 """
1777 if not scanner:
1778 return []
1779
1780
1781
1782
1783
1784
1785
1786
1787 self.clear()
1788 return scanner(self, env, path)
1789
1790
1791
1792
1793
1796
1802
1803
1804
1805
1806
1808 """Create this directory, silently and without worrying about
1809 whether the builder is the default or not."""
1810 listDirs = []
1811 parent = self
1812 while parent:
1813 if parent.exists():
1814 break
1815 listDirs.append(parent)
1816 p = parent.up()
1817 if p is None:
1818
1819
1820 raise SCons.Errors.StopError(parent._path)
1821 parent = p
1822 listDirs.reverse()
1823 for dirnode in listDirs:
1824 try:
1825
1826
1827
1828
1829 SCons.Node.Node.build(dirnode)
1830 dirnode.get_executor().nullify()
1831
1832
1833
1834
1835 dirnode.clear()
1836 except OSError:
1837 pass
1838
1842
1844 """Return any corresponding targets in a variant directory.
1845 """
1846 return self.fs.variant_dir_target_climb(self, self, [])
1847
1849 """A directory does not get scanned."""
1850 return None
1851
1853 """We already emit things in text, so just return the binary
1854 version."""
1855 return self.get_contents()
1856
1857 - def get_contents(self):
1858 """Return content signatures and names of all our children
1859 separated by new-lines. Ensure that the nodes are sorted."""
1860 return SCons.Node._get_contents_map[self._func_get_contents](self)
1861
1863 """Compute the content signature for Directory nodes. In
1864 general, this is not needed and the content signature is not
1865 stored in the DirNodeInfo. However, if get_contents on a Dir
1866 node is called which has a child directory, the child
1867 directory should return the hash of its contents."""
1868 contents = self.get_contents()
1869 return SCons.Util.MD5signature(contents)
1870
1873
1884
1895
1899
1901 """Dir has a special need for srcnode()...if we
1902 have a srcdir attribute set, then that *is* our srcnode."""
1903 if self.srcdir:
1904 return self.srcdir
1905 return Base.srcnode(self)
1906
1908 """Return the latest timestamp from among our children"""
1909 stamp = 0
1910 for kid in self.children():
1911 if kid.get_timestamp() > stamp:
1912 stamp = kid.get_timestamp()
1913 return stamp
1914
1916 """Get the absolute path of the file."""
1917 return self._abspath
1918
1920 """Get the absolute path of the file."""
1921 return self._labspath
1922
1925
1928
1931
1932 - def entry_abspath(self, name):
1933 return self._abspath + OS_SEP + name
1934
1935 - def entry_labspath(self, name):
1936 return self._labspath + '/' + name
1937
1938 - def entry_path(self, name):
1939 return self._path + OS_SEP + name
1940
1941 - def entry_tpath(self, name):
1942 return self._tpath + OS_SEP + name
1943
1944 - def entry_exists_on_disk(self, name):
1945 """ Searches through the file/dir entries of the current
1946 directory, and returns True if a physical entry with the given
1947 name could be found.
1948
1949 @see rentry_exists_on_disk
1950 """
1951 try:
1952 d = self.on_disk_entries
1953 except AttributeError:
1954 d = {}
1955 try:
1956 entries = os.listdir(self._abspath)
1957 except OSError:
1958 pass
1959 else:
1960 for entry in map(_my_normcase, entries):
1961 d[entry] = True
1962 self.on_disk_entries = d
1963 if sys.platform == 'win32' or sys.platform == 'cygwin':
1964 name = _my_normcase(name)
1965 result = d.get(name)
1966 if result is None:
1967
1968
1969 result = os.path.exists(self._abspath + OS_SEP + name)
1970 d[name] = result
1971 return result
1972 else:
1973 return name in d
1974
1975 - def rentry_exists_on_disk(self, name):
1976 """ Searches through the file/dir entries of the current
1977 *and* all its remote directories (repos), and returns
1978 True if a physical entry with the given name could be found.
1979 The local directory (self) gets searched first, so
1980 repositories take a lower precedence regarding the
1981 searching order.
1982
1983 @see entry_exists_on_disk
1984 """
1985
1986 rentry_exists = self.entry_exists_on_disk(name)
1987 if not rentry_exists:
1988
1989 norm_name = _my_normcase(name)
1990 for rdir in self.get_all_rdirs():
1991 try:
1992 node = rdir.entries[norm_name]
1993 if node:
1994 rentry_exists = True
1995 break
1996 except KeyError:
1997 if rdir.entry_exists_on_disk(name):
1998 rentry_exists = True
1999 break
2000 return rentry_exists
2001
2002 @SCons.Memoize.CountMethodCall
2022
2039
2042
2043 @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
2045 try:
2046 memo_dict = self._memo['srcdir_find_file']
2047 except KeyError:
2048 memo_dict = {}
2049 self._memo['srcdir_find_file'] = memo_dict
2050 else:
2051 try:
2052 return memo_dict[filename]
2053 except KeyError:
2054 pass
2055
2056 def func(node):
2057 if (isinstance(node, File) or isinstance(node, Entry)) and \
2058 (node.is_derived() or node.exists()):
2059 return node
2060 return None
2061
2062 norm_name = _my_normcase(filename)
2063
2064 for rdir in self.get_all_rdirs():
2065 try: node = rdir.entries[norm_name]
2066 except KeyError: node = rdir.file_on_disk(filename)
2067 else: node = func(node)
2068 if node:
2069 result = (node, self)
2070 memo_dict[filename] = result
2071 return result
2072
2073 for srcdir in self.srcdir_list():
2074 for rdir in srcdir.get_all_rdirs():
2075 try: node = rdir.entries[norm_name]
2076 except KeyError: node = rdir.file_on_disk(filename)
2077 else: node = func(node)
2078 if node:
2079 result = (File(filename, self, self.fs), srcdir)
2080 memo_dict[filename] = result
2081 return result
2082
2083 result = (None, None)
2084 memo_dict[filename] = result
2085 return result
2086
2095
2106
2107 - def walk(self, func, arg):
2108 """
2109 Walk this directory tree by calling the specified function
2110 for each directory in the tree.
2111
2112 This behaves like the os.path.walk() function, but for in-memory
2113 Node.FS.Dir objects. The function takes the same arguments as
2114 the functions passed to os.path.walk():
2115
2116 func(arg, dirname, fnames)
2117
2118 Except that "dirname" will actually be the directory *Node*,
2119 not the string. The '.' and '..' entries are excluded from
2120 fnames. The fnames list may be modified in-place to filter the
2121 subdirectories visited or otherwise impose a specific order.
2122 The "arg" argument is always passed to func() and may be used
2123 in any way (or ignored, passing None is common).
2124 """
2125 entries = self.entries
2126 names = list(entries.keys())
2127 names.remove('.')
2128 names.remove('..')
2129 func(arg, self, names)
2130 for dirname in [n for n in names if isinstance(entries[n], Dir)]:
2131 entries[dirname].walk(func, arg)
2132
2133 - def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
2134 """
2135 Returns a list of Nodes (or strings) matching a specified
2136 pathname pattern.
2137
2138 Pathname patterns follow UNIX shell semantics: * matches
2139 any-length strings of any characters, ? matches any character,
2140 and [] can enclose lists or ranges of characters. Matches do
2141 not span directory separators.
2142
2143 The matches take into account Repositories, returning local
2144 Nodes if a corresponding entry exists in a Repository (either
2145 an in-memory Node or something on disk).
2146
2147 By defafult, the glob() function matches entries that exist
2148 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
2149 argument to False (or some other non-true value) causes the glob()
2150 function to only match in-memory Nodes. The default behavior is
2151 to return both the on-disk and in-memory Nodes.
2152
2153 The "source" argument, when true, specifies that corresponding
2154 source Nodes must be returned if you're globbing in a build
2155 directory (initialized with VariantDir()). The default behavior
2156 is to return Nodes local to the VariantDir().
2157
2158 The "strings" argument, when true, returns the matches as strings,
2159 not Nodes. The strings are path names relative to this directory.
2160
2161 The "exclude" argument, if not None, must be a pattern or a list
2162 of patterns following the same UNIX shell semantics.
2163 Elements matching a least one pattern of this list will be excluded
2164 from the result.
2165
2166 The underlying algorithm is adapted from the glob.glob() function
2167 in the Python library (but heavily modified), and uses fnmatch()
2168 under the covers.
2169 """
2170 dirname, basename = os.path.split(pathname)
2171 if not dirname:
2172 result = self._glob1(basename, ondisk, source, strings)
2173 else:
2174 if has_glob_magic(dirname):
2175 list = self.glob(dirname, ondisk, source, False, exclude)
2176 else:
2177 list = [self.Dir(dirname, create=True)]
2178 result = []
2179 for dir in list:
2180 r = dir._glob1(basename, ondisk, source, strings)
2181 if strings:
2182 r = [os.path.join(str(dir), x) for x in r]
2183 result.extend(r)
2184 if exclude:
2185 result = filter(lambda x: not any(fnmatch.fnmatch(str(x), e) for e in SCons.Util.flatten(exclude)), result)
2186 return sorted(result, key=lambda a: str(a))
2187
2188 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2189 """
2190 Globs for and returns a list of entry names matching a single
2191 pattern in this directory.
2192
2193 This searches any repositories and source directories for
2194 corresponding entries and returns a Node (or string) relative
2195 to the current directory if an entry is found anywhere.
2196
2197 TODO: handle pattern with no wildcard
2198 """
2199 search_dir_list = self.get_all_rdirs()
2200 for srcdir in self.srcdir_list():
2201 search_dir_list.extend(srcdir.get_all_rdirs())
2202
2203 selfEntry = self.Entry
2204 names = []
2205 for dir in search_dir_list:
2206
2207
2208
2209 node_names = [ v.name for k, v in dir.entries.items()
2210 if k not in ('.', '..') ]
2211 names.extend(node_names)
2212 if not strings:
2213
2214
2215 for name in node_names: selfEntry(name)
2216 if ondisk:
2217 try:
2218 disk_names = os.listdir(dir._abspath)
2219 except os.error:
2220 continue
2221 names.extend(disk_names)
2222 if not strings:
2223
2224
2225
2226
2227
2228
2229
2230
2231 if pattern[0] != '.':
2232
2233 disk_names = [x for x in disk_names if x[0] != '.']
2234 disk_names = fnmatch.filter(disk_names, pattern)
2235 dirEntry = dir.Entry
2236 for name in disk_names:
2237
2238
2239 name = './' + name
2240 node = dirEntry(name).disambiguate()
2241 n = selfEntry(name)
2242 if n.__class__ != node.__class__:
2243 n.__class__ = node.__class__
2244 n._morph()
2245
2246 names = set(names)
2247 if pattern[0] != '.':
2248 names = [x for x in names if x[0] != '.']
2249 names = fnmatch.filter(names, pattern)
2250
2251 if strings:
2252 return names
2253
2254 return [self.entries[_my_normcase(n)] for n in names]
2255
2257 """A class for the root directory of a file system.
2258
2259 This is the same as a Dir class, except that the path separator
2260 ('/' or '\\') is actually part of the name, so we don't need to
2261 add a separator when creating the path names of entries within
2262 this directory.
2263 """
2264
2265 __slots__ = ['_lookupDict']
2266
2320
2362
2363
2368
2370 """
2371 Fast (?) lookup of a *normalized* absolute path.
2372
2373 This method is intended for use by internal lookups with
2374 already-normalized path data. For general-purpose lookups,
2375 use the FS.Entry(), FS.Dir() or FS.File() methods.
2376
2377 The caller is responsible for making sure we're passed a
2378 normalized absolute path; we merely let Python's dictionary look
2379 up and return the One True Node.FS object for the path.
2380
2381 If a Node for the specified "p" doesn't already exist, and
2382 "create" is specified, the Node may be created after recursive
2383 invocation to find or create the parent directory or directories.
2384 """
2385 k = _my_normcase(p)
2386 try:
2387 result = self._lookupDict[k]
2388 except KeyError:
2389 if not create:
2390 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2391 raise SCons.Errors.UserError(msg)
2392
2393
2394 dir_name, file_name = p.rsplit('/',1)
2395 dir_node = self._lookup_abs(dir_name, Dir)
2396 result = klass(file_name, dir_node, self.fs)
2397
2398
2399
2400 result.diskcheck_match()
2401
2402 self._lookupDict[k] = result
2403 dir_node.entries[_my_normcase(file_name)] = result
2404 dir_node.implicit = None
2405 else:
2406
2407
2408 result.must_be_same(klass)
2409 return result
2410
2413
2414 - def entry_abspath(self, name):
2415 return self._abspath + name
2416
2417 - def entry_labspath(self, name):
2419
2420 - def entry_path(self, name):
2421 return self._path + name
2422
2423 - def entry_tpath(self, name):
2424 return self._tpath + name
2425
2427 if self is dir:
2428 return 1
2429 else:
2430 return 0
2431
2434
2437
2440
2442 __slots__ = ('csig', 'timestamp', 'size')
2443 current_version_id = 2
2444
2445 field_list = ['csig', 'timestamp', 'size']
2446
2447
2448 fs = None
2449
2460
2462 """
2463 Return all fields that shall be pickled. Walk the slots in the class
2464 hierarchy and add those to the state dictionary. If a '__dict__' slot is
2465 available, copy all entries to the dictionary. Also include the version
2466 id, which is fixed for all instances of a class.
2467 """
2468 state = getattr(self, '__dict__', {}).copy()
2469 for obj in type(self).mro():
2470 for name in getattr(obj,'__slots__',()):
2471 if hasattr(self, name):
2472 state[name] = getattr(self, name)
2473
2474 state['_version_id'] = self.current_version_id
2475 try:
2476 del state['__weakref__']
2477 except KeyError:
2478 pass
2479
2480 return state
2481
2483 """
2484 Restore the attributes from a pickled state.
2485 """
2486
2487 del state['_version_id']
2488 for key, value in state.items():
2489 if key not in ('__weakref__',):
2490 setattr(self, key, value)
2491
2493 __slots__ = ()
2494 current_version_id = 2
2495
2497 """
2498 Converts this FileBuildInfo object for writing to a .sconsign file
2499
2500 This replaces each Node in our various dependency lists with its
2501 usual string representation: relative to the top-level SConstruct
2502 directory, or an absolute path if it's outside.
2503 """
2504 if os_sep_is_slash:
2505 node_to_str = str
2506 else:
2507 def node_to_str(n):
2508 try:
2509 s = n.get_internal_path()
2510 except AttributeError:
2511 s = str(n)
2512 else:
2513 s = s.replace(OS_SEP, '/')
2514 return s
2515 for attr in ['bsources', 'bdepends', 'bimplicit']:
2516 try:
2517 val = getattr(self, attr)
2518 except AttributeError:
2519 pass
2520 else:
2521 setattr(self, attr, list(map(node_to_str, val)))
2523 """
2524 Converts a newly-read FileBuildInfo object for in-SCons use
2525
2526 For normal up-to-date checking, we don't have any conversion to
2527 perform--but we're leaving this method here to make that clear.
2528 """
2529 pass
2531 """
2532 Prepares a FileBuildInfo object for explaining what changed
2533
2534 The bsources, bdepends and bimplicit lists have all been
2535 stored on disk as paths relative to the top-level SConstruct
2536 directory. Convert the strings to actual Nodes (for use by the
2537 --debug=explain code and --implicit-cache).
2538 """
2539 attrs = [
2540 ('bsources', 'bsourcesigs'),
2541 ('bdepends', 'bdependsigs'),
2542 ('bimplicit', 'bimplicitsigs'),
2543 ]
2544 for (nattr, sattr) in attrs:
2545 try:
2546 strings = getattr(self, nattr)
2547 nodeinfos = getattr(self, sattr)
2548 except AttributeError:
2549 continue
2550 if strings is None or nodeinfos is None:
2551 continue
2552 nodes = []
2553 for s, ni in zip(strings, nodeinfos):
2554 if not isinstance(s, SCons.Node.Node):
2555 s = ni.str_to_node(s)
2556 nodes.append(s)
2557 setattr(self, nattr, nodes)
2569
2571 """A class for files in a file system.
2572 """
2573
2574 __slots__ = ['scanner_paths',
2575 'cachedir_csig',
2576 'cachesig',
2577 'repositories',
2578 'srcdir',
2579 'entries',
2580 'searched',
2581 '_sconsign',
2582 'variant_dirs',
2583 'root',
2584 'dirname',
2585 'on_disk_entries',
2586 'sccs_dir',
2587 'rcs_dir',
2588 'released_target_info',
2589 'contentsig']
2590
2591 NodeInfo = FileNodeInfo
2592 BuildInfo = FileBuildInfo
2593
2594 md5_chunksize = 64
2595
2599
2600 - def __init__(self, name, directory, fs):
2604
2605 - def Entry(self, name):
2606 """Create an entry node named 'name' relative to
2607 the directory of this file."""
2608 return self.dir.Entry(name)
2609
2610 - def Dir(self, name, create=True):
2611 """Create a directory node named 'name' relative to
2612 the directory of this file."""
2613 return self.dir.Dir(name, create=create)
2614
2615 - def Dirs(self, pathlist):
2616 """Create a list of directories relative to the SConscript
2617 directory of this file."""
2618 return [self.Dir(p) for p in pathlist]
2619
2620 - def File(self, name):
2621 """Create a file node named 'name' relative to
2622 the directory of this file."""
2623 return self.dir.File(name)
2624
2625
2626
2627
2628
2629
2630
2631
2660
2663
2664 - def get_contents(self):
2666
2667
2668
2669
2671 contents = self.get_contents()
2672
2673
2674
2675
2676
2677
2678 if contents.startswith(codecs.BOM_UTF8):
2679 return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2680 if contents.startswith(codecs.BOM_UTF16_LE):
2681 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2682 if contents.startswith(codecs.BOM_UTF16_BE):
2683 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2684 return contents
2685
2686 - def get_content_hash(self):
2687 """
2688 Compute and return the MD5 hash for this file.
2689 """
2690 if not self.rexists():
2691 return SCons.Util.MD5signature('')
2692 fname = self.rfile().get_abspath()
2693 try:
2694 cs = SCons.Util.MD5filesignature(fname,
2695 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2696 except EnvironmentError, e:
2697 if not e.filename:
2698 e.filename = fname
2699 raise
2700 return cs
2701
2702 @SCons.Memoize.CountMethodCall
2704 try:
2705 return self._memo['get_size']
2706 except KeyError:
2707 pass
2708
2709 if self.rexists():
2710 size = self.rfile().getsize()
2711 else:
2712 size = 0
2713
2714 self._memo['get_size'] = size
2715
2716 return size
2717
2718 @SCons.Memoize.CountMethodCall
2733
2734 convert_copy_attrs = [
2735 'bsources',
2736 'bimplicit',
2737 'bdepends',
2738 'bact',
2739 'bactsig',
2740 'ninfo',
2741 ]
2742
2743
2744 convert_sig_attrs = [
2745 'bsourcesigs',
2746 'bimplicitsigs',
2747 'bdependsigs',
2748 ]
2749
2750 - def convert_old_entry(self, old_entry):
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
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 import SCons.SConsign
2819 new_entry = SCons.SConsign.SConsignEntry()
2820 new_entry.binfo = self.new_binfo()
2821 binfo = new_entry.binfo
2822 for attr in self.convert_copy_attrs:
2823 try:
2824 value = getattr(old_entry, attr)
2825 except AttributeError:
2826 continue
2827 setattr(binfo, attr, value)
2828 delattr(old_entry, attr)
2829 for attr in self.convert_sig_attrs:
2830 try:
2831 sig_list = getattr(old_entry, attr)
2832 except AttributeError:
2833 continue
2834 value = []
2835 for sig in sig_list:
2836 ninfo = self.new_ninfo()
2837 if len(sig) == 32:
2838 ninfo.csig = sig
2839 else:
2840 ninfo.timestamp = sig
2841 value.append(ninfo)
2842 setattr(binfo, attr, value)
2843 delattr(old_entry, attr)
2844 return new_entry
2845
2846 @SCons.Memoize.CountMethodCall
2873
2879
2882
2884 return (id(env), id(scanner), path)
2885
2886 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2888 """Return the included implicit dependencies in this file.
2889 Cache results so we only scan the file once per path
2890 regardless of how many times this information is requested.
2891 """
2892 memo_key = (id(env), id(scanner), path)
2893 try:
2894 memo_dict = self._memo['get_found_includes']
2895 except KeyError:
2896 memo_dict = {}
2897 self._memo['get_found_includes'] = memo_dict
2898 else:
2899 try:
2900 return memo_dict[memo_key]
2901 except KeyError:
2902 pass
2903
2904 if scanner:
2905
2906 result = scanner(self, env, path)
2907 result = [N.disambiguate() for N in result]
2908 else:
2909 result = []
2910
2911 memo_dict[memo_key] = result
2912
2913 return result
2914
2919
2935
2937 """Try to retrieve the node's content from a cache
2938
2939 This method is called from multiple threads in a parallel build,
2940 so only do thread safe stuff here. Do thread unsafe stuff in
2941 built().
2942
2943 Returns true if the node was successfully retrieved.
2944 """
2945 if self.nocache:
2946 return None
2947 if not self.is_derived():
2948 return None
2949 return self.get_build_env().get_CacheDir().retrieve(self)
2950
2973
2975 """Called just after this node has been marked
2976 up-to-date or was built completely.
2977
2978 This is where we try to release as many target node infos
2979 as possible for clean builds and update runs, in order
2980 to minimize the overall memory consumption.
2981
2982 We'd like to remove a lot more attributes like self.sources
2983 and self.sources_set, but they might get used
2984 in a next build step. For example, during configuration
2985 the source files for a built *.o file are used to figure out
2986 which linker to use for the resulting Program (gcc vs. g++)!
2987 That's why we check for the 'keep_targetinfo' attribute,
2988 config Nodes and the Interactive mode just don't allow
2989 an early release of most variables.
2990
2991 In the same manner, we can't simply remove the self.attributes
2992 here. The smart linking relies on the shared flag, and some
2993 parts of the java Tool use it to transport information
2994 about nodes...
2995
2996 @see: built() and Node.release_target_info()
2997 """
2998 if (self.released_target_info or SCons.Node.interactive):
2999 return
3000
3001 if not hasattr(self.attributes, 'keep_targetinfo'):
3002
3003
3004 self.changed(allowcache=True)
3005 self.get_contents_sig()
3006 self.get_build_env()
3007
3008 self.executor = None
3009 self._memo.pop('rfile', None)
3010 self.prerequisites = None
3011
3012 if not len(self.ignore_set):
3013 self.ignore_set = None
3014 if not len(self.implicit_set):
3015 self.implicit_set = None
3016 if not len(self.depends_set):
3017 self.depends_set = None
3018 if not len(self.ignore):
3019 self.ignore = None
3020 if not len(self.depends):
3021 self.depends = None
3022
3023
3024 self.released_target_info = True
3025
3045
3047 """Return whether this Node has a source builder or not.
3048
3049 If this Node doesn't have an explicit source code builder, this
3050 is where we figure out, on the fly, if there's a transparent
3051 source code builder for it.
3052
3053 Note that if we found a source builder, we also set the
3054 self.builder attribute, so that all of the methods that actually
3055 *build* this file don't have to do anything different.
3056 """
3057 try:
3058 scb = self.sbuilder
3059 except AttributeError:
3060 scb = self.sbuilder = self.find_src_builder()
3061 return scb is not None
3062
3069
3077
3078
3079
3080
3081
3085
3100
3101
3102
3103
3104
3111
3127
3128 @SCons.Memoize.CountMethodCall
3138
3139
3140
3141
3142
3144 """
3145 Returns the content signature currently stored for this node
3146 if it's been unmodified longer than the max_drift value, or the
3147 max_drift value is 0. Returns None otherwise.
3148 """
3149 old = self.get_stored_info()
3150 mtime = self.get_timestamp()
3151
3152 max_drift = self.fs.max_drift
3153 if max_drift > 0:
3154 if (time.time() - mtime) > max_drift:
3155 try:
3156 n = old.ninfo
3157 if n.timestamp and n.csig and n.timestamp == mtime:
3158 return n.csig
3159 except AttributeError:
3160 pass
3161 elif max_drift == 0:
3162 try:
3163 return old.ninfo.csig
3164 except AttributeError:
3165 pass
3166
3167 return None
3168
3205
3206
3207
3208
3209
3213
3237
3238 - def changed(self, node=None, allowcache=False):
3239 """
3240 Returns if the node is up-to-date with respect to the BuildInfo
3241 stored last time it was built.
3242
3243 For File nodes this is basically a wrapper around Node.changed(),
3244 but we allow the return value to get cached after the reference
3245 to the Executor got released in release_target_info().
3246
3247 @see: Node.changed()
3248 """
3249 if node is None:
3250 try:
3251 return self._memo['changed']
3252 except KeyError:
3253 pass
3254
3255 has_changed = SCons.Node.Node.changed(self, node)
3256 if allowcache:
3257 self._memo['changed'] = has_changed
3258 return has_changed
3259
3260 - def changed_content(self, target, prev_ni):
3261 cur_csig = self.get_csig()
3262 try:
3263 return cur_csig != prev_ni.csig
3264 except AttributeError:
3265 return 1
3266
3269
3270 - def changed_timestamp_then_content(self, target, prev_ni):
3271 if not self.changed_timestamp_match(target, prev_ni):
3272 try:
3273 self.get_ninfo().csig = prev_ni.csig
3274 except AttributeError:
3275 pass
3276 return False
3277 return self.changed_content(target, prev_ni)
3278
3284
3290
3318
3319 @SCons.Memoize.CountMethodCall
3351
3353 return str(self.rfile())
3354
3356 """
3357 Fetch a Node's content signature for purposes of computing
3358 another Node's cachesig.
3359
3360 This is a wrapper around the normal get_csig() method that handles
3361 the somewhat obscure case of using CacheDir with the -n option.
3362 Any files that don't exist would normally be "built" by fetching
3363 them from the cache, but the normal get_csig() method will try
3364 to open up the local file, which doesn't exist because the -n
3365 option meant we didn't actually pull the file from cachedir.
3366 But since the file *does* actually exist in the cachedir, we
3367 can use its contents for the csig.
3368 """
3369 try:
3370 return self.cachedir_csig
3371 except AttributeError:
3372 pass
3373
3374 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
3375 if not self.exists() and cachefile and os.path.exists(cachefile):
3376 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
3377 SCons.Node.FS.File.md5_chunksize * 1024)
3378 else:
3379 self.cachedir_csig = self.get_csig()
3380 return self.cachedir_csig
3381
3382 - def get_contents_sig(self):
3383 """
3384 A helper method for get_cachedir_bsig.
3385
3386 It computes and returns the signature for this
3387 node's contents.
3388 """
3389
3390 try:
3391 return self.contentsig
3392 except AttributeError:
3393 pass
3394
3395 executor = self.get_executor()
3396
3397 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
3398 return result
3399
3401 """
3402 Return the signature for a cached file, including
3403 its children.
3404
3405 It adds the path of the cached file to the cache signature,
3406 because multiple targets built by the same action will all
3407 have the same build signature, and we have to differentiate
3408 them somehow.
3409 """
3410 try:
3411 return self.cachesig
3412 except AttributeError:
3413 pass
3414
3415
3416 children = self.children()
3417 sigs = [n.get_cachedir_csig() for n in children]
3418
3419 sigs.append(self.get_contents_sig())
3420
3421 sigs.append(self.get_internal_path())
3422
3423 result = self.cachesig = SCons.Util.MD5collect(sigs)
3424 return result
3425
3426 default_fs = None
3433
3435 """
3436 """
3437
3440
3442 """
3443 A helper method for find_file() that looks up a directory for
3444 a file we're trying to find. This only creates the Dir Node if
3445 it exists on-disk, since if the directory doesn't exist we know
3446 we won't find any files in it... :-)
3447
3448 It would be more compact to just use this as a nested function
3449 with a default keyword argument (see the commented-out version
3450 below), but that doesn't work unless you have nested scopes,
3451 so we define it here just so this work under Python 1.5.2.
3452 """
3453 if fd is None:
3454 fd = self.default_filedir
3455 dir, name = os.path.split(fd)
3456 drive, d = _my_splitdrive(dir)
3457 if not name and d[:1] in ('/', OS_SEP):
3458
3459 return p.fs.get_root(drive)
3460 if dir:
3461 p = self.filedir_lookup(p, dir)
3462 if not p:
3463 return None
3464 norm_name = _my_normcase(name)
3465 try:
3466 node = p.entries[norm_name]
3467 except KeyError:
3468 return p.dir_on_disk(name)
3469 if isinstance(node, Dir):
3470 return node
3471 if isinstance(node, Entry):
3472 node.must_be_same(Dir)
3473 return node
3474 return None
3475
3477 return (filename, paths)
3478
3479 @SCons.Memoize.CountDictCall(_find_file_key)
3480 - def find_file(self, filename, paths, verbose=None):
3481 """
3482 find_file(str, [Dir()]) -> [nodes]
3483
3484 filename - a filename to find
3485 paths - a list of directory path *nodes* to search in. Can be
3486 represented as a list, a tuple, or a callable that is
3487 called with no arguments and returns the list or tuple.
3488
3489 returns - the node created from the found file.
3490
3491 Find a node corresponding to either a derived file or a file
3492 that exists already.
3493
3494 Only the first file found is returned, and none is returned
3495 if no file is found.
3496 """
3497 memo_key = self._find_file_key(filename, paths)
3498 try:
3499 memo_dict = self._memo['find_file']
3500 except KeyError:
3501 memo_dict = {}
3502 self._memo['find_file'] = memo_dict
3503 else:
3504 try:
3505 return memo_dict[memo_key]
3506 except KeyError:
3507 pass
3508
3509 if verbose and not callable(verbose):
3510 if not SCons.Util.is_String(verbose):
3511 verbose = "find_file"
3512 _verbose = u' %s: ' % verbose
3513 verbose = lambda s: sys.stdout.write(_verbose + s)
3514
3515 filedir, filename = os.path.split(filename)
3516 if filedir:
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547 self.default_filedir = filedir
3548 paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3549
3550 result = None
3551 for dir in paths:
3552 if verbose:
3553 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3554 node, d = dir.srcdir_find_file(filename)
3555 if node:
3556 if verbose:
3557 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3558 result = node
3559 break
3560
3561 memo_dict[memo_key] = result
3562
3563 return result
3564
3565 find_file = FileFinder().find_file
3569 """
3570 Invalidate the memoized values of all Nodes (files or directories)
3571 that are associated with the given entries. Has been added to
3572 clear the cache of nodes affected by a direct execution of an
3573 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3574 inconsistent if the action is run through Execute(). The argument
3575 `targets` can be a single Node object or filename, or a sequence
3576 of Nodes/filenames.
3577 """
3578 from traceback import extract_stack
3579
3580
3581
3582
3583
3584
3585 for f in extract_stack():
3586 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3587 break
3588 else:
3589
3590 return
3591
3592 if not SCons.Util.is_List(targets):
3593 targets = [targets]
3594
3595 for entry in targets:
3596
3597
3598 try:
3599 entry.clear_memoized_values()
3600 except AttributeError:
3601
3602
3603
3604 node = get_default_fs().Entry(entry)
3605 if node:
3606 node.clear_memoized_values()
3607
3608
3609
3610
3611
3612
3613