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.1:3453:73fefd3ea0b0 2015/11/09 03:25:05 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 excludes = []
2186 excludeList = SCons.Util.flatten(exclude)
2187 for x in excludeList:
2188 r = self.glob(x, ondisk, source, strings)
2189 excludes.extend(r)
2190 result = filter(lambda x: not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes)), result)
2191 return sorted(result, key=lambda a: str(a))
2192
2193 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2194 """
2195 Globs for and returns a list of entry names matching a single
2196 pattern in this directory.
2197
2198 This searches any repositories and source directories for
2199 corresponding entries and returns a Node (or string) relative
2200 to the current directory if an entry is found anywhere.
2201
2202 TODO: handle pattern with no wildcard
2203 """
2204 search_dir_list = self.get_all_rdirs()
2205 for srcdir in self.srcdir_list():
2206 search_dir_list.extend(srcdir.get_all_rdirs())
2207
2208 selfEntry = self.Entry
2209 names = []
2210 for dir in search_dir_list:
2211
2212
2213
2214 node_names = [ v.name for k, v in dir.entries.items()
2215 if k not in ('.', '..') ]
2216 names.extend(node_names)
2217 if not strings:
2218
2219
2220 for name in node_names: selfEntry(name)
2221 if ondisk:
2222 try:
2223 disk_names = os.listdir(dir._abspath)
2224 except os.error:
2225 continue
2226 names.extend(disk_names)
2227 if not strings:
2228
2229
2230
2231
2232
2233
2234
2235
2236 if pattern[0] != '.':
2237
2238 disk_names = [x for x in disk_names if x[0] != '.']
2239 disk_names = fnmatch.filter(disk_names, pattern)
2240 dirEntry = dir.Entry
2241 for name in disk_names:
2242
2243
2244 name = './' + name
2245 node = dirEntry(name).disambiguate()
2246 n = selfEntry(name)
2247 if n.__class__ != node.__class__:
2248 n.__class__ = node.__class__
2249 n._morph()
2250
2251 names = set(names)
2252 if pattern[0] != '.':
2253 names = [x for x in names if x[0] != '.']
2254 names = fnmatch.filter(names, pattern)
2255
2256 if strings:
2257 return names
2258
2259 return [self.entries[_my_normcase(n)] for n in names]
2260
2262 """A class for the root directory of a file system.
2263
2264 This is the same as a Dir class, except that the path separator
2265 ('/' or '\\') is actually part of the name, so we don't need to
2266 add a separator when creating the path names of entries within
2267 this directory.
2268 """
2269
2270 __slots__ = ['_lookupDict']
2271
2325
2367
2368
2373
2375 """
2376 Fast (?) lookup of a *normalized* absolute path.
2377
2378 This method is intended for use by internal lookups with
2379 already-normalized path data. For general-purpose lookups,
2380 use the FS.Entry(), FS.Dir() or FS.File() methods.
2381
2382 The caller is responsible for making sure we're passed a
2383 normalized absolute path; we merely let Python's dictionary look
2384 up and return the One True Node.FS object for the path.
2385
2386 If a Node for the specified "p" doesn't already exist, and
2387 "create" is specified, the Node may be created after recursive
2388 invocation to find or create the parent directory or directories.
2389 """
2390 k = _my_normcase(p)
2391 try:
2392 result = self._lookupDict[k]
2393 except KeyError:
2394 if not create:
2395 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2396 raise SCons.Errors.UserError(msg)
2397
2398
2399 dir_name, file_name = p.rsplit('/',1)
2400 dir_node = self._lookup_abs(dir_name, Dir)
2401 result = klass(file_name, dir_node, self.fs)
2402
2403
2404
2405 result.diskcheck_match()
2406
2407 self._lookupDict[k] = result
2408 dir_node.entries[_my_normcase(file_name)] = result
2409 dir_node.implicit = None
2410 else:
2411
2412
2413 result.must_be_same(klass)
2414 return result
2415
2418
2419 - def entry_abspath(self, name):
2420 return self._abspath + name
2421
2422 - def entry_labspath(self, name):
2424
2425 - def entry_path(self, name):
2426 return self._path + name
2427
2428 - def entry_tpath(self, name):
2429 return self._tpath + name
2430
2432 if self is dir:
2433 return 1
2434 else:
2435 return 0
2436
2439
2442
2445
2447 __slots__ = ('csig', 'timestamp', 'size')
2448 current_version_id = 2
2449
2450 field_list = ['csig', 'timestamp', 'size']
2451
2452
2453 fs = None
2454
2465
2467 """
2468 Return all fields that shall be pickled. Walk the slots in the class
2469 hierarchy and add those to the state dictionary. If a '__dict__' slot is
2470 available, copy all entries to the dictionary. Also include the version
2471 id, which is fixed for all instances of a class.
2472 """
2473 state = getattr(self, '__dict__', {}).copy()
2474 for obj in type(self).mro():
2475 for name in getattr(obj,'__slots__',()):
2476 if hasattr(self, name):
2477 state[name] = getattr(self, name)
2478
2479 state['_version_id'] = self.current_version_id
2480 try:
2481 del state['__weakref__']
2482 except KeyError:
2483 pass
2484
2485 return state
2486
2488 """
2489 Restore the attributes from a pickled state.
2490 """
2491
2492 del state['_version_id']
2493 for key, value in state.items():
2494 if key not in ('__weakref__',):
2495 setattr(self, key, value)
2496
2498 __slots__ = ()
2499 current_version_id = 2
2500
2502 """
2503 Converts this FileBuildInfo object for writing to a .sconsign file
2504
2505 This replaces each Node in our various dependency lists with its
2506 usual string representation: relative to the top-level SConstruct
2507 directory, or an absolute path if it's outside.
2508 """
2509 if os_sep_is_slash:
2510 node_to_str = str
2511 else:
2512 def node_to_str(n):
2513 try:
2514 s = n.get_internal_path()
2515 except AttributeError:
2516 s = str(n)
2517 else:
2518 s = s.replace(OS_SEP, '/')
2519 return s
2520 for attr in ['bsources', 'bdepends', 'bimplicit']:
2521 try:
2522 val = getattr(self, attr)
2523 except AttributeError:
2524 pass
2525 else:
2526 setattr(self, attr, list(map(node_to_str, val)))
2528 """
2529 Converts a newly-read FileBuildInfo object for in-SCons use
2530
2531 For normal up-to-date checking, we don't have any conversion to
2532 perform--but we're leaving this method here to make that clear.
2533 """
2534 pass
2536 """
2537 Prepares a FileBuildInfo object for explaining what changed
2538
2539 The bsources, bdepends and bimplicit lists have all been
2540 stored on disk as paths relative to the top-level SConstruct
2541 directory. Convert the strings to actual Nodes (for use by the
2542 --debug=explain code and --implicit-cache).
2543 """
2544 attrs = [
2545 ('bsources', 'bsourcesigs'),
2546 ('bdepends', 'bdependsigs'),
2547 ('bimplicit', 'bimplicitsigs'),
2548 ]
2549 for (nattr, sattr) in attrs:
2550 try:
2551 strings = getattr(self, nattr)
2552 nodeinfos = getattr(self, sattr)
2553 except AttributeError:
2554 continue
2555 if strings is None or nodeinfos is None:
2556 continue
2557 nodes = []
2558 for s, ni in zip(strings, nodeinfos):
2559 if not isinstance(s, SCons.Node.Node):
2560 s = ni.str_to_node(s)
2561 nodes.append(s)
2562 setattr(self, nattr, nodes)
2574
2576 """A class for files in a file system.
2577 """
2578
2579 __slots__ = ['scanner_paths',
2580 'cachedir_csig',
2581 'cachesig',
2582 'repositories',
2583 'srcdir',
2584 'entries',
2585 'searched',
2586 '_sconsign',
2587 'variant_dirs',
2588 'root',
2589 'dirname',
2590 'on_disk_entries',
2591 'sccs_dir',
2592 'rcs_dir',
2593 'released_target_info',
2594 'contentsig']
2595
2596 NodeInfo = FileNodeInfo
2597 BuildInfo = FileBuildInfo
2598
2599 md5_chunksize = 64
2600
2604
2605 - def __init__(self, name, directory, fs):
2609
2610 - def Entry(self, name):
2611 """Create an entry node named 'name' relative to
2612 the directory of this file."""
2613 return self.dir.Entry(name)
2614
2615 - def Dir(self, name, create=True):
2616 """Create a directory node named 'name' relative to
2617 the directory of this file."""
2618 return self.dir.Dir(name, create=create)
2619
2620 - def Dirs(self, pathlist):
2621 """Create a list of directories relative to the SConscript
2622 directory of this file."""
2623 return [self.Dir(p) for p in pathlist]
2624
2625 - def File(self, name):
2626 """Create a file node named 'name' relative to
2627 the directory of this file."""
2628 return self.dir.File(name)
2629
2630
2631
2632
2633
2634
2635
2636
2665
2668
2669 - def get_contents(self):
2671
2672
2673
2674
2676 contents = self.get_contents()
2677
2678
2679
2680
2681
2682
2683 if contents.startswith(codecs.BOM_UTF8):
2684 return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2685 if contents.startswith(codecs.BOM_UTF16_LE):
2686 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2687 if contents.startswith(codecs.BOM_UTF16_BE):
2688 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2689 return contents
2690
2691 - def get_content_hash(self):
2692 """
2693 Compute and return the MD5 hash for this file.
2694 """
2695 if not self.rexists():
2696 return SCons.Util.MD5signature('')
2697 fname = self.rfile().get_abspath()
2698 try:
2699 cs = SCons.Util.MD5filesignature(fname,
2700 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2701 except EnvironmentError, e:
2702 if not e.filename:
2703 e.filename = fname
2704 raise
2705 return cs
2706
2707 @SCons.Memoize.CountMethodCall
2709 try:
2710 return self._memo['get_size']
2711 except KeyError:
2712 pass
2713
2714 if self.rexists():
2715 size = self.rfile().getsize()
2716 else:
2717 size = 0
2718
2719 self._memo['get_size'] = size
2720
2721 return size
2722
2723 @SCons.Memoize.CountMethodCall
2738
2739 convert_copy_attrs = [
2740 'bsources',
2741 'bimplicit',
2742 'bdepends',
2743 'bact',
2744 'bactsig',
2745 'ninfo',
2746 ]
2747
2748
2749 convert_sig_attrs = [
2750 'bsourcesigs',
2751 'bimplicitsigs',
2752 'bdependsigs',
2753 ]
2754
2755 - def convert_old_entry(self, old_entry):
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
2819
2820
2821
2822
2823 import SCons.SConsign
2824 new_entry = SCons.SConsign.SConsignEntry()
2825 new_entry.binfo = self.new_binfo()
2826 binfo = new_entry.binfo
2827 for attr in self.convert_copy_attrs:
2828 try:
2829 value = getattr(old_entry, attr)
2830 except AttributeError:
2831 continue
2832 setattr(binfo, attr, value)
2833 delattr(old_entry, attr)
2834 for attr in self.convert_sig_attrs:
2835 try:
2836 sig_list = getattr(old_entry, attr)
2837 except AttributeError:
2838 continue
2839 value = []
2840 for sig in sig_list:
2841 ninfo = self.new_ninfo()
2842 if len(sig) == 32:
2843 ninfo.csig = sig
2844 else:
2845 ninfo.timestamp = sig
2846 value.append(ninfo)
2847 setattr(binfo, attr, value)
2848 delattr(old_entry, attr)
2849 return new_entry
2850
2851 @SCons.Memoize.CountMethodCall
2878
2884
2887
2889 return (id(env), id(scanner), path)
2890
2891 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2893 """Return the included implicit dependencies in this file.
2894 Cache results so we only scan the file once per path
2895 regardless of how many times this information is requested.
2896 """
2897 memo_key = (id(env), id(scanner), path)
2898 try:
2899 memo_dict = self._memo['get_found_includes']
2900 except KeyError:
2901 memo_dict = {}
2902 self._memo['get_found_includes'] = memo_dict
2903 else:
2904 try:
2905 return memo_dict[memo_key]
2906 except KeyError:
2907 pass
2908
2909 if scanner:
2910
2911 result = scanner(self, env, path)
2912 result = [N.disambiguate() for N in result]
2913 else:
2914 result = []
2915
2916 memo_dict[memo_key] = result
2917
2918 return result
2919
2924
2940
2942 """Try to retrieve the node's content from a cache
2943
2944 This method is called from multiple threads in a parallel build,
2945 so only do thread safe stuff here. Do thread unsafe stuff in
2946 built().
2947
2948 Returns true if the node was successfully retrieved.
2949 """
2950 if self.nocache:
2951 return None
2952 if not self.is_derived():
2953 return None
2954 return self.get_build_env().get_CacheDir().retrieve(self)
2955
2978
2980 """Called just after this node has been marked
2981 up-to-date or was built completely.
2982
2983 This is where we try to release as many target node infos
2984 as possible for clean builds and update runs, in order
2985 to minimize the overall memory consumption.
2986
2987 We'd like to remove a lot more attributes like self.sources
2988 and self.sources_set, but they might get used
2989 in a next build step. For example, during configuration
2990 the source files for a built *.o file are used to figure out
2991 which linker to use for the resulting Program (gcc vs. g++)!
2992 That's why we check for the 'keep_targetinfo' attribute,
2993 config Nodes and the Interactive mode just don't allow
2994 an early release of most variables.
2995
2996 In the same manner, we can't simply remove the self.attributes
2997 here. The smart linking relies on the shared flag, and some
2998 parts of the java Tool use it to transport information
2999 about nodes...
3000
3001 @see: built() and Node.release_target_info()
3002 """
3003 if (self.released_target_info or SCons.Node.interactive):
3004 return
3005
3006 if not hasattr(self.attributes, 'keep_targetinfo'):
3007
3008
3009 self.changed(allowcache=True)
3010 self.get_contents_sig()
3011 self.get_build_env()
3012
3013 self.executor = None
3014 self._memo.pop('rfile', None)
3015 self.prerequisites = None
3016
3017 if not len(self.ignore_set):
3018 self.ignore_set = None
3019 if not len(self.implicit_set):
3020 self.implicit_set = None
3021 if not len(self.depends_set):
3022 self.depends_set = None
3023 if not len(self.ignore):
3024 self.ignore = None
3025 if not len(self.depends):
3026 self.depends = None
3027
3028
3029 self.released_target_info = True
3030
3050
3052 """Return whether this Node has a source builder or not.
3053
3054 If this Node doesn't have an explicit source code builder, this
3055 is where we figure out, on the fly, if there's a transparent
3056 source code builder for it.
3057
3058 Note that if we found a source builder, we also set the
3059 self.builder attribute, so that all of the methods that actually
3060 *build* this file don't have to do anything different.
3061 """
3062 try:
3063 scb = self.sbuilder
3064 except AttributeError:
3065 scb = self.sbuilder = self.find_src_builder()
3066 return scb is not None
3067
3074
3082
3083
3084
3085
3086
3090
3105
3106
3107
3108
3109
3116
3132
3133 @SCons.Memoize.CountMethodCall
3143
3144
3145
3146
3147
3149 """
3150 Returns the content signature currently stored for this node
3151 if it's been unmodified longer than the max_drift value, or the
3152 max_drift value is 0. Returns None otherwise.
3153 """
3154 old = self.get_stored_info()
3155 mtime = self.get_timestamp()
3156
3157 max_drift = self.fs.max_drift
3158 if max_drift > 0:
3159 if (time.time() - mtime) > max_drift:
3160 try:
3161 n = old.ninfo
3162 if n.timestamp and n.csig and n.timestamp == mtime:
3163 return n.csig
3164 except AttributeError:
3165 pass
3166 elif max_drift == 0:
3167 try:
3168 return old.ninfo.csig
3169 except AttributeError:
3170 pass
3171
3172 return None
3173
3210
3211
3212
3213
3214
3218
3242
3243 - def changed(self, node=None, allowcache=False):
3244 """
3245 Returns if the node is up-to-date with respect to the BuildInfo
3246 stored last time it was built.
3247
3248 For File nodes this is basically a wrapper around Node.changed(),
3249 but we allow the return value to get cached after the reference
3250 to the Executor got released in release_target_info().
3251
3252 @see: Node.changed()
3253 """
3254 if node is None:
3255 try:
3256 return self._memo['changed']
3257 except KeyError:
3258 pass
3259
3260 has_changed = SCons.Node.Node.changed(self, node)
3261 if allowcache:
3262 self._memo['changed'] = has_changed
3263 return has_changed
3264
3265 - def changed_content(self, target, prev_ni):
3266 cur_csig = self.get_csig()
3267 try:
3268 return cur_csig != prev_ni.csig
3269 except AttributeError:
3270 return 1
3271
3274
3275 - def changed_timestamp_then_content(self, target, prev_ni):
3276 if not self.changed_timestamp_match(target, prev_ni):
3277 try:
3278 self.get_ninfo().csig = prev_ni.csig
3279 except AttributeError:
3280 pass
3281 return False
3282 return self.changed_content(target, prev_ni)
3283
3289
3295
3323
3324 @SCons.Memoize.CountMethodCall
3356
3358 return str(self.rfile())
3359
3361 """
3362 Fetch a Node's content signature for purposes of computing
3363 another Node's cachesig.
3364
3365 This is a wrapper around the normal get_csig() method that handles
3366 the somewhat obscure case of using CacheDir with the -n option.
3367 Any files that don't exist would normally be "built" by fetching
3368 them from the cache, but the normal get_csig() method will try
3369 to open up the local file, which doesn't exist because the -n
3370 option meant we didn't actually pull the file from cachedir.
3371 But since the file *does* actually exist in the cachedir, we
3372 can use its contents for the csig.
3373 """
3374 try:
3375 return self.cachedir_csig
3376 except AttributeError:
3377 pass
3378
3379 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
3380 if not self.exists() and cachefile and os.path.exists(cachefile):
3381 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
3382 SCons.Node.FS.File.md5_chunksize * 1024)
3383 else:
3384 self.cachedir_csig = self.get_csig()
3385 return self.cachedir_csig
3386
3387 - def get_contents_sig(self):
3388 """
3389 A helper method for get_cachedir_bsig.
3390
3391 It computes and returns the signature for this
3392 node's contents.
3393 """
3394
3395 try:
3396 return self.contentsig
3397 except AttributeError:
3398 pass
3399
3400 executor = self.get_executor()
3401
3402 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
3403 return result
3404
3406 """
3407 Return the signature for a cached file, including
3408 its children.
3409
3410 It adds the path of the cached file to the cache signature,
3411 because multiple targets built by the same action will all
3412 have the same build signature, and we have to differentiate
3413 them somehow.
3414 """
3415 try:
3416 return self.cachesig
3417 except AttributeError:
3418 pass
3419
3420
3421 children = self.children()
3422 sigs = [n.get_cachedir_csig() for n in children]
3423
3424 sigs.append(self.get_contents_sig())
3425
3426 sigs.append(self.get_internal_path())
3427
3428 result = self.cachesig = SCons.Util.MD5collect(sigs)
3429 return result
3430
3431 default_fs = None
3438
3440 """
3441 """
3442
3445
3447 """
3448 A helper method for find_file() that looks up a directory for
3449 a file we're trying to find. This only creates the Dir Node if
3450 it exists on-disk, since if the directory doesn't exist we know
3451 we won't find any files in it... :-)
3452
3453 It would be more compact to just use this as a nested function
3454 with a default keyword argument (see the commented-out version
3455 below), but that doesn't work unless you have nested scopes,
3456 so we define it here just so this work under Python 1.5.2.
3457 """
3458 if fd is None:
3459 fd = self.default_filedir
3460 dir, name = os.path.split(fd)
3461 drive, d = _my_splitdrive(dir)
3462 if not name and d[:1] in ('/', OS_SEP):
3463
3464 return p.fs.get_root(drive)
3465 if dir:
3466 p = self.filedir_lookup(p, dir)
3467 if not p:
3468 return None
3469 norm_name = _my_normcase(name)
3470 try:
3471 node = p.entries[norm_name]
3472 except KeyError:
3473 return p.dir_on_disk(name)
3474 if isinstance(node, Dir):
3475 return node
3476 if isinstance(node, Entry):
3477 node.must_be_same(Dir)
3478 return node
3479 return None
3480
3482 return (filename, paths)
3483
3484 @SCons.Memoize.CountDictCall(_find_file_key)
3485 - def find_file(self, filename, paths, verbose=None):
3486 """
3487 find_file(str, [Dir()]) -> [nodes]
3488
3489 filename - a filename to find
3490 paths - a list of directory path *nodes* to search in. Can be
3491 represented as a list, a tuple, or a callable that is
3492 called with no arguments and returns the list or tuple.
3493
3494 returns - the node created from the found file.
3495
3496 Find a node corresponding to either a derived file or a file
3497 that exists already.
3498
3499 Only the first file found is returned, and none is returned
3500 if no file is found.
3501 """
3502 memo_key = self._find_file_key(filename, paths)
3503 try:
3504 memo_dict = self._memo['find_file']
3505 except KeyError:
3506 memo_dict = {}
3507 self._memo['find_file'] = memo_dict
3508 else:
3509 try:
3510 return memo_dict[memo_key]
3511 except KeyError:
3512 pass
3513
3514 if verbose and not callable(verbose):
3515 if not SCons.Util.is_String(verbose):
3516 verbose = "find_file"
3517 _verbose = u' %s: ' % verbose
3518 verbose = lambda s: sys.stdout.write(_verbose + s)
3519
3520 filedir, filename = os.path.split(filename)
3521 if filedir:
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
3548
3549
3550
3551
3552 self.default_filedir = filedir
3553 paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3554
3555 result = None
3556 for dir in paths:
3557 if verbose:
3558 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3559 node, d = dir.srcdir_find_file(filename)
3560 if node:
3561 if verbose:
3562 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3563 result = node
3564 break
3565
3566 memo_dict[memo_key] = result
3567
3568 return result
3569
3570 find_file = FileFinder().find_file
3574 """
3575 Invalidate the memoized values of all Nodes (files or directories)
3576 that are associated with the given entries. Has been added to
3577 clear the cache of nodes affected by a direct execution of an
3578 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3579 inconsistent if the action is run through Execute(). The argument
3580 `targets` can be a single Node object or filename, or a sequence
3581 of Nodes/filenames.
3582 """
3583 from traceback import extract_stack
3584
3585
3586
3587
3588
3589
3590 for f in extract_stack():
3591 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3592 break
3593 else:
3594
3595 return
3596
3597 if not SCons.Util.is_List(targets):
3598 targets = [targets]
3599
3600 for entry in targets:
3601
3602
3603 try:
3604 entry.clear_memoized_values()
3605 except AttributeError:
3606
3607
3608
3609 node = get_default_fs().Entry(entry)
3610 if node:
3611 node.clear_memoized_values()
3612
3613
3614
3615
3616
3617
3618