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.5.1:3735:9dc6cee5c168 2016/11/03 14:02:02 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 explicitly 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 -class FS(LocalFS):
1169
1171 """Initialize the Node.FS subsystem.
1172
1173 The supplied path is the top of the source tree, where we
1174 expect to find the top-level build file. If no path is
1175 supplied, the current directory is the default.
1176
1177 The path argument must be a valid absolute path.
1178 """
1179 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS')
1180
1181 self._memo = {}
1182
1183 self.Root = {}
1184 self.SConstruct_dir = None
1185 self.max_drift = default_max_drift
1186
1187 self.Top = None
1188 if path is None:
1189 self.pathTop = os.getcwd()
1190 else:
1191 self.pathTop = path
1192 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1193
1194 self.Top = self.Dir(self.pathTop)
1195 self.Top._path = '.'
1196 self.Top._tpath = '.'
1197 self._cwd = self.Top
1198
1199 DirNodeInfo.fs = self
1200 FileNodeInfo.fs = self
1201
1203 self.SConstruct_dir = dir
1204
1206 return self.max_drift
1207
1209 self.max_drift = max_drift
1210
1212 if hasattr(self, "_cwd"):
1213 return self._cwd
1214 else:
1215 return "<no cwd>"
1216
1217 - def chdir(self, dir, change_os_dir=0):
1218 """Change the current working directory for lookups.
1219 If change_os_dir is true, we will also change the "real" cwd
1220 to match.
1221 """
1222 curr=self._cwd
1223 try:
1224 if dir is not None:
1225 self._cwd = dir
1226 if change_os_dir:
1227 os.chdir(dir.get_abspath())
1228 except OSError:
1229 self._cwd = curr
1230 raise
1231
1233 """
1234 Returns the root directory for the specified drive, creating
1235 it if necessary.
1236 """
1237 drive = _my_normcase(drive)
1238 try:
1239 return self.Root[drive]
1240 except KeyError:
1241 root = RootDir(drive, self)
1242 self.Root[drive] = root
1243 if not drive:
1244 self.Root[self.defaultDrive] = root
1245 elif drive == self.defaultDrive:
1246 self.Root[''] = root
1247 return root
1248
1249 - def _lookup(self, p, directory, fsclass, create=1):
1250 """
1251 The generic entry point for Node lookup with user-supplied data.
1252
1253 This translates arbitrary input into a canonical Node.FS object
1254 of the specified fsclass. The general approach for strings is
1255 to turn it into a fully normalized absolute path and then call
1256 the root directory's lookup_abs() method for the heavy lifting.
1257
1258 If the path name begins with '#', it is unconditionally
1259 interpreted relative to the top-level directory of this FS. '#'
1260 is treated as a synonym for the top-level SConstruct directory,
1261 much like '~' is treated as a synonym for the user's home
1262 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1263 to the 'foo' subdirectory underneath the top-level SConstruct
1264 directory.
1265
1266 If the path name is relative, then the path is looked up relative
1267 to the specified directory, or the current directory (self._cwd,
1268 typically the SConscript directory) if the specified directory
1269 is None.
1270 """
1271 if isinstance(p, Base):
1272
1273
1274 p.must_be_same(fsclass)
1275 return p
1276
1277 p = str(p)
1278
1279 if not os_sep_is_slash:
1280 p = p.replace(OS_SEP, '/')
1281
1282 if p[0:1] == '#':
1283
1284
1285
1286 p = p[1:]
1287 directory = self.Top
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299 if do_splitdrive:
1300 drive, p = _my_splitdrive(p)
1301 if drive:
1302 root = self.get_root(drive)
1303 else:
1304 root = directory.root
1305 else:
1306 root = directory.root
1307
1308
1309
1310 p = p.strip('/')
1311
1312 needs_normpath = needs_normpath_match(p)
1313
1314
1315 if p in ('', '.'):
1316 p = directory.get_labspath()
1317 else:
1318 p = directory.get_labspath() + '/' + p
1319 else:
1320 if do_splitdrive:
1321 drive, p = _my_splitdrive(p)
1322 if drive and not p:
1323
1324
1325
1326 p = '/'
1327 else:
1328 drive = ''
1329
1330
1331
1332 if p != '/':
1333 p = p.rstrip('/')
1334
1335 needs_normpath = needs_normpath_match(p)
1336
1337 if p[0:1] == '/':
1338
1339 root = self.get_root(drive)
1340 else:
1341
1342
1343
1344
1345 if directory:
1346 if not isinstance(directory, Dir):
1347 directory = self.Dir(directory)
1348 else:
1349 directory = self._cwd
1350
1351 if p in ('', '.'):
1352 p = directory.get_labspath()
1353 else:
1354 p = directory.get_labspath() + '/' + p
1355
1356 if drive:
1357 root = self.get_root(drive)
1358 else:
1359 root = directory.root
1360
1361 if needs_normpath is not None:
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371 ins = p.split('/')[1:]
1372 outs = []
1373 for d in ins:
1374 if d == '..':
1375 try:
1376 outs.pop()
1377 except IndexError:
1378 pass
1379 elif d not in ('', '.'):
1380 outs.append(d)
1381 p = '/' + '/'.join(outs)
1382
1383 return root._lookup_abs(p, fsclass, create)
1384
1385 - def Entry(self, name, directory = None, create = 1):
1386 """Look up or create a generic Entry node with the specified name.
1387 If the name is a relative path (begins with ./, ../, or a file
1388 name), then it is looked up relative to the supplied directory
1389 node, or to the top level directory of the FS (supplied at
1390 construction time) if no directory is supplied.
1391 """
1392 return self._lookup(name, directory, Entry, create)
1393
1394 - def File(self, name, directory = None, create = 1):
1395 """Look up or create a File node with the specified name. If
1396 the name is a relative path (begins with ./, ../, or a file name),
1397 then it is looked up relative to the supplied directory node,
1398 or to the top level directory of the FS (supplied at construction
1399 time) if no directory is supplied.
1400
1401 This method will raise TypeError if a directory is found at the
1402 specified path.
1403 """
1404 return self._lookup(name, directory, File, create)
1405
1406 - def Dir(self, name, directory = None, create = True):
1407 """Look up or create a Dir node with the specified name. If
1408 the name is a relative path (begins with ./, ../, or a file name),
1409 then it is looked up relative to the supplied directory node,
1410 or to the top level directory of the FS (supplied at construction
1411 time) if no directory is supplied.
1412
1413 This method will raise TypeError if a normal file is found at the
1414 specified path.
1415 """
1416 return self._lookup(name, directory, Dir, create)
1417
1418 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1419 """Link the supplied variant directory to the source directory
1420 for purposes of building files."""
1421
1422 if not isinstance(src_dir, SCons.Node.Node):
1423 src_dir = self.Dir(src_dir)
1424 if not isinstance(variant_dir, SCons.Node.Node):
1425 variant_dir = self.Dir(variant_dir)
1426 if src_dir.is_under(variant_dir):
1427 raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
1428 if variant_dir.srcdir:
1429 if variant_dir.srcdir == src_dir:
1430 return
1431 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
1432 variant_dir.link(src_dir, duplicate)
1433
1440
1442 """Create targets in corresponding variant directories
1443
1444 Climb the directory tree, and look up path names
1445 relative to any linked variant directories we find.
1446
1447 Even though this loops and walks up the tree, we don't memoize
1448 the return value because this is really only used to process
1449 the command-line targets.
1450 """
1451 targets = []
1452 message = None
1453 fmt = "building associated VariantDir targets: %s"
1454 start_dir = dir
1455 while dir:
1456 for bd in dir.variant_dirs:
1457 if start_dir.is_under(bd):
1458
1459 return [orig], fmt % str(orig)
1460 p = os.path.join(bd._path, *tail)
1461 targets.append(self.Entry(p))
1462 tail = [dir.name] + tail
1463 dir = dir.up()
1464 if targets:
1465 message = fmt % ' '.join(map(str, targets))
1466 return targets, message
1467
1468 - def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
1469 """
1470 Globs
1471
1472 This is mainly a shim layer
1473 """
1474 if cwd is None:
1475 cwd = self.getcwd()
1476 return cwd.glob(pathname, ondisk, source, strings, exclude)
1477
1495
1499
1500 glob_magic_check = re.compile('[*?[]')
1504
1506 """A class for directories in a file system.
1507 """
1508
1509 __slots__ = ['scanner_paths',
1510 'cachedir_csig',
1511 'cachesig',
1512 'repositories',
1513 'srcdir',
1514 'entries',
1515 'searched',
1516 '_sconsign',
1517 'variant_dirs',
1518 'root',
1519 'dirname',
1520 'on_disk_entries',
1521 'sccs_dir',
1522 'rcs_dir',
1523 'released_target_info',
1524 'contentsig']
1525
1526 NodeInfo = DirNodeInfo
1527 BuildInfo = DirBuildInfo
1528
1529 - def __init__(self, name, directory, fs):
1533
1599
1603
1605 """Called when we change the repository(ies) for a directory.
1606 This clears any cached information that is invalidated by changing
1607 the repository."""
1608
1609 for node in self.entries.values():
1610 if node != self.dir:
1611 if node != self and isinstance(node, Dir):
1612 node.__clearRepositoryCache(duplicate)
1613 else:
1614 node.clear()
1615 try:
1616 del node._srcreps
1617 except AttributeError:
1618 pass
1619 if duplicate is not None:
1620 node.duplicate=duplicate
1621
1625
1626 - def Entry(self, name):
1627 """
1628 Looks up or creates an entry node named 'name' relative to
1629 this directory.
1630 """
1631 return self.fs.Entry(name, self)
1632
1633 - def Dir(self, name, create=True):
1634 """
1635 Looks up or creates a directory node named 'name' relative to
1636 this directory.
1637 """
1638 return self.fs.Dir(name, self, create)
1639
1640 - def File(self, name):
1641 """
1642 Looks up or creates a file node named 'name' relative to
1643 this directory.
1644 """
1645 return self.fs.File(name, self)
1646
1647 - def link(self, srcdir, duplicate):
1654
1661
1662 @SCons.Memoize.CountMethodCall
1684
1690
1693
1696
1697 @SCons.Memoize.CountDictCall(_rel_path_key)
1699 """Return a path to "other" relative to this directory.
1700 """
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712 try:
1713 memo_dict = self._memo['rel_path']
1714 except KeyError:
1715 memo_dict = {}
1716 self._memo['rel_path'] = memo_dict
1717 else:
1718 try:
1719 return memo_dict[other]
1720 except KeyError:
1721 pass
1722
1723 if self is other:
1724 result = '.'
1725
1726 elif not other in self._path_elements:
1727 try:
1728 other_dir = other.get_dir()
1729 except AttributeError:
1730 result = str(other)
1731 else:
1732 if other_dir is None:
1733 result = other.name
1734 else:
1735 dir_rel_path = self.rel_path(other_dir)
1736 if dir_rel_path == '.':
1737 result = other.name
1738 else:
1739 result = dir_rel_path + OS_SEP + other.name
1740 else:
1741 i = self._path_elements.index(other) + 1
1742
1743 path_elems = ['..'] * (len(self._path_elements) - i) \
1744 + [n.name for n in other._path_elements[i:]]
1745
1746 result = OS_SEP.join(path_elems)
1747
1748 memo_dict[other] = result
1749
1750 return result
1751
1755
1759
1761 """Return this directory's implicit dependencies.
1762
1763 We don't bother caching the results because the scan typically
1764 shouldn't be requested more than once (as opposed to scanning
1765 .h file contents, which can be requested as many times as the
1766 files is #included by other files).
1767 """
1768 if not scanner:
1769 return []
1770
1771
1772
1773
1774
1775
1776
1777
1778 self.clear()
1779 return scanner(self, env, path)
1780
1781
1782
1783
1784
1787
1793
1794
1795
1796
1797
1799 """Create this directory, silently and without worrying about
1800 whether the builder is the default or not."""
1801 listDirs = []
1802 parent = self
1803 while parent:
1804 if parent.exists():
1805 break
1806 listDirs.append(parent)
1807 p = parent.up()
1808 if p is None:
1809
1810
1811 raise SCons.Errors.StopError(parent._path)
1812 parent = p
1813 listDirs.reverse()
1814 for dirnode in listDirs:
1815 try:
1816
1817
1818
1819
1820 SCons.Node.Node.build(dirnode)
1821 dirnode.get_executor().nullify()
1822
1823
1824
1825
1826 dirnode.clear()
1827 except OSError:
1828 pass
1829
1833
1835 """Return any corresponding targets in a variant directory.
1836 """
1837 return self.fs.variant_dir_target_climb(self, self, [])
1838
1840 """A directory does not get scanned."""
1841 return None
1842
1844 """We already emit things in text, so just return the binary
1845 version."""
1846 return self.get_contents()
1847
1848 - def get_contents(self):
1849 """Return content signatures and names of all our children
1850 separated by new-lines. Ensure that the nodes are sorted."""
1851 return SCons.Node._get_contents_map[self._func_get_contents](self)
1852
1854 """Compute the content signature for Directory nodes. In
1855 general, this is not needed and the content signature is not
1856 stored in the DirNodeInfo. However, if get_contents on a Dir
1857 node is called which has a child directory, the child
1858 directory should return the hash of its contents."""
1859 contents = self.get_contents()
1860 return SCons.Util.MD5signature(contents)
1861
1864
1875
1886
1890
1892 """Dir has a special need for srcnode()...if we
1893 have a srcdir attribute set, then that *is* our srcnode."""
1894 if self.srcdir:
1895 return self.srcdir
1896 return Base.srcnode(self)
1897
1899 """Return the latest timestamp from among our children"""
1900 stamp = 0
1901 for kid in self.children():
1902 if kid.get_timestamp() > stamp:
1903 stamp = kid.get_timestamp()
1904 return stamp
1905
1907 """Get the absolute path of the file."""
1908 return self._abspath
1909
1911 """Get the absolute path of the file."""
1912 return self._labspath
1913
1916
1919
1922
1923 - def entry_abspath(self, name):
1924 return self._abspath + OS_SEP + name
1925
1926 - def entry_labspath(self, name):
1927 return self._labspath + '/' + name
1928
1929 - def entry_path(self, name):
1930 return self._path + OS_SEP + name
1931
1932 - def entry_tpath(self, name):
1933 return self._tpath + OS_SEP + name
1934
1935 - def entry_exists_on_disk(self, name):
1936 """ Searches through the file/dir entries of the current
1937 directory, and returns True if a physical entry with the given
1938 name could be found.
1939
1940 @see rentry_exists_on_disk
1941 """
1942 try:
1943 d = self.on_disk_entries
1944 except AttributeError:
1945 d = {}
1946 try:
1947 entries = os.listdir(self._abspath)
1948 except OSError:
1949 pass
1950 else:
1951 for entry in map(_my_normcase, entries):
1952 d[entry] = True
1953 self.on_disk_entries = d
1954 if sys.platform == 'win32' or sys.platform == 'cygwin':
1955 name = _my_normcase(name)
1956 result = d.get(name)
1957 if result is None:
1958
1959
1960 result = os.path.exists(self._abspath + OS_SEP + name)
1961 d[name] = result
1962 return result
1963 else:
1964 return name in d
1965
1966 - def rentry_exists_on_disk(self, name):
1967 """ Searches through the file/dir entries of the current
1968 *and* all its remote directories (repos), and returns
1969 True if a physical entry with the given name could be found.
1970 The local directory (self) gets searched first, so
1971 repositories take a lower precedence regarding the
1972 searching order.
1973
1974 @see entry_exists_on_disk
1975 """
1976
1977 rentry_exists = self.entry_exists_on_disk(name)
1978 if not rentry_exists:
1979
1980 norm_name = _my_normcase(name)
1981 for rdir in self.get_all_rdirs():
1982 try:
1983 node = rdir.entries[norm_name]
1984 if node:
1985 rentry_exists = True
1986 break
1987 except KeyError:
1988 if rdir.entry_exists_on_disk(name):
1989 rentry_exists = True
1990 break
1991 return rentry_exists
1992
1993 @SCons.Memoize.CountMethodCall
2013
2030
2033
2034 @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
2036 try:
2037 memo_dict = self._memo['srcdir_find_file']
2038 except KeyError:
2039 memo_dict = {}
2040 self._memo['srcdir_find_file'] = memo_dict
2041 else:
2042 try:
2043 return memo_dict[filename]
2044 except KeyError:
2045 pass
2046
2047 def func(node):
2048 if (isinstance(node, File) or isinstance(node, Entry)) and \
2049 (node.is_derived() or node.exists()):
2050 return node
2051 return None
2052
2053 norm_name = _my_normcase(filename)
2054
2055 for rdir in self.get_all_rdirs():
2056 try: node = rdir.entries[norm_name]
2057 except KeyError: node = rdir.file_on_disk(filename)
2058 else: node = func(node)
2059 if node:
2060 result = (node, self)
2061 memo_dict[filename] = result
2062 return result
2063
2064 for srcdir in self.srcdir_list():
2065 for rdir in srcdir.get_all_rdirs():
2066 try: node = rdir.entries[norm_name]
2067 except KeyError: node = rdir.file_on_disk(filename)
2068 else: node = func(node)
2069 if node:
2070 result = (File(filename, self, self.fs), srcdir)
2071 memo_dict[filename] = result
2072 return result
2073
2074 result = (None, None)
2075 memo_dict[filename] = result
2076 return result
2077
2086
2097
2098 - def walk(self, func, arg):
2099 """
2100 Walk this directory tree by calling the specified function
2101 for each directory in the tree.
2102
2103 This behaves like the os.path.walk() function, but for in-memory
2104 Node.FS.Dir objects. The function takes the same arguments as
2105 the functions passed to os.path.walk():
2106
2107 func(arg, dirname, fnames)
2108
2109 Except that "dirname" will actually be the directory *Node*,
2110 not the string. The '.' and '..' entries are excluded from
2111 fnames. The fnames list may be modified in-place to filter the
2112 subdirectories visited or otherwise impose a specific order.
2113 The "arg" argument is always passed to func() and may be used
2114 in any way (or ignored, passing None is common).
2115 """
2116 entries = self.entries
2117 names = list(entries.keys())
2118 names.remove('.')
2119 names.remove('..')
2120 func(arg, self, names)
2121 for dirname in [n for n in names if isinstance(entries[n], Dir)]:
2122 entries[dirname].walk(func, arg)
2123
2124 - def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
2125 """
2126 Returns a list of Nodes (or strings) matching a specified
2127 pathname pattern.
2128
2129 Pathname patterns follow UNIX shell semantics: * matches
2130 any-length strings of any characters, ? matches any character,
2131 and [] can enclose lists or ranges of characters. Matches do
2132 not span directory separators.
2133
2134 The matches take into account Repositories, returning local
2135 Nodes if a corresponding entry exists in a Repository (either
2136 an in-memory Node or something on disk).
2137
2138 By defafult, the glob() function matches entries that exist
2139 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
2140 argument to False (or some other non-true value) causes the glob()
2141 function to only match in-memory Nodes. The default behavior is
2142 to return both the on-disk and in-memory Nodes.
2143
2144 The "source" argument, when true, specifies that corresponding
2145 source Nodes must be returned if you're globbing in a build
2146 directory (initialized with VariantDir()). The default behavior
2147 is to return Nodes local to the VariantDir().
2148
2149 The "strings" argument, when true, returns the matches as strings,
2150 not Nodes. The strings are path names relative to this directory.
2151
2152 The "exclude" argument, if not None, must be a pattern or a list
2153 of patterns following the same UNIX shell semantics.
2154 Elements matching a least one pattern of this list will be excluded
2155 from the result.
2156
2157 The underlying algorithm is adapted from the glob.glob() function
2158 in the Python library (but heavily modified), and uses fnmatch()
2159 under the covers.
2160 """
2161 dirname, basename = os.path.split(pathname)
2162 if not dirname:
2163 result = self._glob1(basename, ondisk, source, strings)
2164 else:
2165 if has_glob_magic(dirname):
2166 list = self.glob(dirname, ondisk, source, False, exclude)
2167 else:
2168 list = [self.Dir(dirname, create=True)]
2169 result = []
2170 for dir in list:
2171 r = dir._glob1(basename, ondisk, source, strings)
2172 if strings:
2173 r = [os.path.join(str(dir), x) for x in r]
2174 result.extend(r)
2175 if exclude:
2176 excludes = []
2177 excludeList = SCons.Util.flatten(exclude)
2178 for x in excludeList:
2179 r = self.glob(x, ondisk, source, strings)
2180 excludes.extend(r)
2181 result = filter(lambda x: not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes)), result)
2182 return sorted(result, key=lambda a: str(a))
2183
2184 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2185 """
2186 Globs for and returns a list of entry names matching a single
2187 pattern in this directory.
2188
2189 This searches any repositories and source directories for
2190 corresponding entries and returns a Node (or string) relative
2191 to the current directory if an entry is found anywhere.
2192
2193 TODO: handle pattern with no wildcard
2194 """
2195 search_dir_list = self.get_all_rdirs()
2196 for srcdir in self.srcdir_list():
2197 search_dir_list.extend(srcdir.get_all_rdirs())
2198
2199 selfEntry = self.Entry
2200 names = []
2201 for dir in search_dir_list:
2202
2203
2204
2205 node_names = [ v.name for k, v in dir.entries.items()
2206 if k not in ('.', '..') ]
2207 names.extend(node_names)
2208 if not strings:
2209
2210
2211 for name in node_names: selfEntry(name)
2212 if ondisk:
2213 try:
2214 disk_names = os.listdir(dir._abspath)
2215 except os.error:
2216 continue
2217 names.extend(disk_names)
2218 if not strings:
2219
2220
2221
2222
2223
2224
2225
2226
2227 if pattern[0] != '.':
2228 disk_names = [x for x in disk_names if x[0] != '.']
2229 disk_names = fnmatch.filter(disk_names, pattern)
2230 dirEntry = dir.Entry
2231 for name in disk_names:
2232
2233
2234 name = './' + name
2235 node = dirEntry(name).disambiguate()
2236 n = selfEntry(name)
2237 if n.__class__ != node.__class__:
2238 n.__class__ = node.__class__
2239 n._morph()
2240
2241 names = set(names)
2242 if pattern[0] != '.':
2243 names = [x for x in names if x[0] != '.']
2244 names = fnmatch.filter(names, pattern)
2245
2246 if strings:
2247 return names
2248
2249 return [self.entries[_my_normcase(n)] for n in names]
2250
2252 """A class for the root directory of a file system.
2253
2254 This is the same as a Dir class, except that the path separator
2255 ('/' or '\\') is actually part of the name, so we don't need to
2256 add a separator when creating the path names of entries within
2257 this directory.
2258 """
2259
2260 __slots__ = ['_lookupDict']
2261
2315
2357
2358
2363
2365 """
2366 Fast (?) lookup of a *normalized* absolute path.
2367
2368 This method is intended for use by internal lookups with
2369 already-normalized path data. For general-purpose lookups,
2370 use the FS.Entry(), FS.Dir() or FS.File() methods.
2371
2372 The caller is responsible for making sure we're passed a
2373 normalized absolute path; we merely let Python's dictionary look
2374 up and return the One True Node.FS object for the path.
2375
2376 If a Node for the specified "p" doesn't already exist, and
2377 "create" is specified, the Node may be created after recursive
2378 invocation to find or create the parent directory or directories.
2379 """
2380 k = _my_normcase(p)
2381 try:
2382 result = self._lookupDict[k]
2383 except KeyError:
2384 if not create:
2385 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2386 raise SCons.Errors.UserError(msg)
2387
2388
2389 dir_name, file_name = p.rsplit('/',1)
2390 dir_node = self._lookup_abs(dir_name, Dir)
2391 result = klass(file_name, dir_node, self.fs)
2392
2393
2394
2395 result.diskcheck_match()
2396
2397 self._lookupDict[k] = result
2398 dir_node.entries[_my_normcase(file_name)] = result
2399 dir_node.implicit = None
2400 else:
2401
2402
2403 result.must_be_same(klass)
2404 return result
2405
2408
2409 - def entry_abspath(self, name):
2410 return self._abspath + name
2411
2412 - def entry_labspath(self, name):
2414
2415 - def entry_path(self, name):
2416 return self._path + name
2417
2418 - def entry_tpath(self, name):
2419 return self._tpath + name
2420
2422 if self is dir:
2423 return 1
2424 else:
2425 return 0
2426
2429
2432
2435
2437 __slots__ = ('csig', 'timestamp', 'size')
2438 current_version_id = 2
2439
2440 field_list = ['csig', 'timestamp', 'size']
2441
2442
2443 fs = None
2444
2455
2457 """
2458 Return all fields that shall be pickled. Walk the slots in the class
2459 hierarchy and add those to the state dictionary. If a '__dict__' slot is
2460 available, copy all entries to the dictionary. Also include the version
2461 id, which is fixed for all instances of a class.
2462 """
2463 state = getattr(self, '__dict__', {}).copy()
2464 for obj in type(self).mro():
2465 for name in getattr(obj,'__slots__',()):
2466 if hasattr(self, name):
2467 state[name] = getattr(self, name)
2468
2469 state['_version_id'] = self.current_version_id
2470 try:
2471 del state['__weakref__']
2472 except KeyError:
2473 pass
2474
2475 return state
2476
2478 """
2479 Restore the attributes from a pickled state.
2480 """
2481
2482 del state['_version_id']
2483 for key, value in state.items():
2484 if key not in ('__weakref__',):
2485 setattr(self, key, value)
2486
2488 __slots__ = ()
2489 current_version_id = 2
2490
2492 """
2493 Converts this FileBuildInfo object for writing to a .sconsign file
2494
2495 This replaces each Node in our various dependency lists with its
2496 usual string representation: relative to the top-level SConstruct
2497 directory, or an absolute path if it's outside.
2498 """
2499 if os_sep_is_slash:
2500 node_to_str = str
2501 else:
2502 def node_to_str(n):
2503 try:
2504 s = n.get_internal_path()
2505 except AttributeError:
2506 s = str(n)
2507 else:
2508 s = s.replace(OS_SEP, '/')
2509 return s
2510 for attr in ['bsources', 'bdepends', 'bimplicit']:
2511 try:
2512 val = getattr(self, attr)
2513 except AttributeError:
2514 pass
2515 else:
2516 setattr(self, attr, list(map(node_to_str, val)))
2518 """
2519 Converts a newly-read FileBuildInfo object for in-SCons use
2520
2521 For normal up-to-date checking, we don't have any conversion to
2522 perform--but we're leaving this method here to make that clear.
2523 """
2524 pass
2526 """
2527 Prepares a FileBuildInfo object for explaining what changed
2528
2529 The bsources, bdepends and bimplicit lists have all been
2530 stored on disk as paths relative to the top-level SConstruct
2531 directory. Convert the strings to actual Nodes (for use by the
2532 --debug=explain code and --implicit-cache).
2533 """
2534 attrs = [
2535 ('bsources', 'bsourcesigs'),
2536 ('bdepends', 'bdependsigs'),
2537 ('bimplicit', 'bimplicitsigs'),
2538 ]
2539 for (nattr, sattr) in attrs:
2540 try:
2541 strings = getattr(self, nattr)
2542 nodeinfos = getattr(self, sattr)
2543 except AttributeError:
2544 continue
2545 if strings is None or nodeinfos is None:
2546 continue
2547 nodes = []
2548 for s, ni in zip(strings, nodeinfos):
2549 if not isinstance(s, SCons.Node.Node):
2550 s = ni.str_to_node(s)
2551 nodes.append(s)
2552 setattr(self, nattr, nodes)
2564
2566 """A class for files in a file system.
2567 """
2568
2569 __slots__ = ['scanner_paths',
2570 'cachedir_csig',
2571 'cachesig',
2572 'repositories',
2573 'srcdir',
2574 'entries',
2575 'searched',
2576 '_sconsign',
2577 'variant_dirs',
2578 'root',
2579 'dirname',
2580 'on_disk_entries',
2581 'sccs_dir',
2582 'rcs_dir',
2583 'released_target_info',
2584 'contentsig']
2585
2586 NodeInfo = FileNodeInfo
2587 BuildInfo = FileBuildInfo
2588
2589 md5_chunksize = 64
2590
2594
2595 - def __init__(self, name, directory, fs):
2599
2600 - def Entry(self, name):
2601 """Create an entry node named 'name' relative to
2602 the directory of this file."""
2603 return self.dir.Entry(name)
2604
2605 - def Dir(self, name, create=True):
2606 """Create a directory node named 'name' relative to
2607 the directory of this file."""
2608 return self.dir.Dir(name, create=create)
2609
2610 - def Dirs(self, pathlist):
2611 """Create a list of directories relative to the SConscript
2612 directory of this file."""
2613 return [self.Dir(p) for p in pathlist]
2614
2615 - def File(self, name):
2616 """Create a file node named 'name' relative to
2617 the directory of this file."""
2618 return self.dir.File(name)
2619
2648
2651
2652 - def get_contents(self):
2654
2655
2656
2657
2659 contents = self.get_contents()
2660
2661
2662
2663
2664
2665
2666 if contents.startswith(codecs.BOM_UTF8):
2667 return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2668 if contents.startswith(codecs.BOM_UTF16_LE):
2669 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2670 if contents.startswith(codecs.BOM_UTF16_BE):
2671 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2672 return contents
2673
2674 - def get_content_hash(self):
2675 """
2676 Compute and return the MD5 hash for this file.
2677 """
2678 if not self.rexists():
2679 return SCons.Util.MD5signature('')
2680 fname = self.rfile().get_abspath()
2681 try:
2682 cs = SCons.Util.MD5filesignature(fname,
2683 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2684 except EnvironmentError, e:
2685 if not e.filename:
2686 e.filename = fname
2687 raise
2688 return cs
2689
2690 @SCons.Memoize.CountMethodCall
2692 try:
2693 return self._memo['get_size']
2694 except KeyError:
2695 pass
2696
2697 if self.rexists():
2698 size = self.rfile().getsize()
2699 else:
2700 size = 0
2701
2702 self._memo['get_size'] = size
2703
2704 return size
2705
2706 @SCons.Memoize.CountMethodCall
2721
2722 convert_copy_attrs = [
2723 'bsources',
2724 'bimplicit',
2725 'bdepends',
2726 'bact',
2727 'bactsig',
2728 'ninfo',
2729 ]
2730
2731
2732 convert_sig_attrs = [
2733 'bsourcesigs',
2734 'bimplicitsigs',
2735 'bdependsigs',
2736 ]
2737
2738 - def convert_old_entry(self, old_entry):
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
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 import SCons.SConsign
2807 new_entry = SCons.SConsign.SConsignEntry()
2808 new_entry.binfo = self.new_binfo()
2809 binfo = new_entry.binfo
2810 for attr in self.convert_copy_attrs:
2811 try:
2812 value = getattr(old_entry, attr)
2813 except AttributeError:
2814 continue
2815 setattr(binfo, attr, value)
2816 delattr(old_entry, attr)
2817 for attr in self.convert_sig_attrs:
2818 try:
2819 sig_list = getattr(old_entry, attr)
2820 except AttributeError:
2821 continue
2822 value = []
2823 for sig in sig_list:
2824 ninfo = self.new_ninfo()
2825 if len(sig) == 32:
2826 ninfo.csig = sig
2827 else:
2828 ninfo.timestamp = sig
2829 value.append(ninfo)
2830 setattr(binfo, attr, value)
2831 delattr(old_entry, attr)
2832 return new_entry
2833
2834 @SCons.Memoize.CountMethodCall
2861
2867
2870
2872 return (id(env), id(scanner), path)
2873
2874 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2876 """Return the included implicit dependencies in this file.
2877 Cache results so we only scan the file once per path
2878 regardless of how many times this information is requested.
2879 """
2880 memo_key = (id(env), id(scanner), path)
2881 try:
2882 memo_dict = self._memo['get_found_includes']
2883 except KeyError:
2884 memo_dict = {}
2885 self._memo['get_found_includes'] = memo_dict
2886 else:
2887 try:
2888 return memo_dict[memo_key]
2889 except KeyError:
2890 pass
2891
2892 if scanner:
2893 result = [n.disambiguate() for n in scanner(self, env, path)]
2894 else:
2895 result = []
2896
2897 memo_dict[memo_key] = result
2898
2899 return result
2900
2905
2921
2923 """Try to retrieve the node's content from a cache
2924
2925 This method is called from multiple threads in a parallel build,
2926 so only do thread safe stuff here. Do thread unsafe stuff in
2927 built().
2928
2929 Returns true if the node was successfully retrieved.
2930 """
2931 if self.nocache:
2932 return None
2933 if not self.is_derived():
2934 return None
2935 return self.get_build_env().get_CacheDir().retrieve(self)
2936
2959
2961 """Called just after this node has been marked
2962 up-to-date or was built completely.
2963
2964 This is where we try to release as many target node infos
2965 as possible for clean builds and update runs, in order
2966 to minimize the overall memory consumption.
2967
2968 We'd like to remove a lot more attributes like self.sources
2969 and self.sources_set, but they might get used
2970 in a next build step. For example, during configuration
2971 the source files for a built *.o file are used to figure out
2972 which linker to use for the resulting Program (gcc vs. g++)!
2973 That's why we check for the 'keep_targetinfo' attribute,
2974 config Nodes and the Interactive mode just don't allow
2975 an early release of most variables.
2976
2977 In the same manner, we can't simply remove the self.attributes
2978 here. The smart linking relies on the shared flag, and some
2979 parts of the java Tool use it to transport information
2980 about nodes...
2981
2982 @see: built() and Node.release_target_info()
2983 """
2984 if (self.released_target_info or SCons.Node.interactive):
2985 return
2986
2987 if not hasattr(self.attributes, 'keep_targetinfo'):
2988
2989
2990 self.changed(allowcache=True)
2991 self.get_contents_sig()
2992 self.get_build_env()
2993
2994 self.executor = None
2995 self._memo.pop('rfile', None)
2996 self.prerequisites = None
2997
2998 if not len(self.ignore_set):
2999 self.ignore_set = None
3000 if not len(self.implicit_set):
3001 self.implicit_set = None
3002 if not len(self.depends_set):
3003 self.depends_set = None
3004 if not len(self.ignore):
3005 self.ignore = None
3006 if not len(self.depends):
3007 self.depends = None
3008
3009
3010 self.released_target_info = True
3011
3031
3033 """Return whether this Node has a source builder or not.
3034
3035 If this Node doesn't have an explicit source code builder, this
3036 is where we figure out, on the fly, if there's a transparent
3037 source code builder for it.
3038
3039 Note that if we found a source builder, we also set the
3040 self.builder attribute, so that all of the methods that actually
3041 *build* this file don't have to do anything different.
3042 """
3043 try:
3044 scb = self.sbuilder
3045 except AttributeError:
3046 scb = self.sbuilder = self.find_src_builder()
3047 return scb is not None
3048
3055
3063
3064
3065
3066
3067
3071
3086
3087
3088
3089
3090
3097
3113
3114 @SCons.Memoize.CountMethodCall
3124
3125
3126
3127
3128
3130 """
3131 Returns the content signature currently stored for this node
3132 if it's been unmodified longer than the max_drift value, or the
3133 max_drift value is 0. Returns None otherwise.
3134 """
3135 old = self.get_stored_info()
3136 mtime = self.get_timestamp()
3137
3138 max_drift = self.fs.max_drift
3139 if max_drift > 0:
3140 if (time.time() - mtime) > max_drift:
3141 try:
3142 n = old.ninfo
3143 if n.timestamp and n.csig and n.timestamp == mtime:
3144 return n.csig
3145 except AttributeError:
3146 pass
3147 elif max_drift == 0:
3148 try:
3149 return old.ninfo.csig
3150 except AttributeError:
3151 pass
3152
3153 return None
3154
3191
3192
3193
3194
3195
3199
3223
3224 - def changed(self, node=None, allowcache=False):
3225 """
3226 Returns if the node is up-to-date with respect to the BuildInfo
3227 stored last time it was built.
3228
3229 For File nodes this is basically a wrapper around Node.changed(),
3230 but we allow the return value to get cached after the reference
3231 to the Executor got released in release_target_info().
3232
3233 @see: Node.changed()
3234 """
3235 if node is None:
3236 try:
3237 return self._memo['changed']
3238 except KeyError:
3239 pass
3240
3241 has_changed = SCons.Node.Node.changed(self, node)
3242 if allowcache:
3243 self._memo['changed'] = has_changed
3244 return has_changed
3245
3246 - def changed_content(self, target, prev_ni):
3247 cur_csig = self.get_csig()
3248 try:
3249 return cur_csig != prev_ni.csig
3250 except AttributeError:
3251 return 1
3252
3255
3256 - def changed_timestamp_then_content(self, target, prev_ni):
3257 if not self.changed_timestamp_match(target, prev_ni):
3258 try:
3259 self.get_ninfo().csig = prev_ni.csig
3260 except AttributeError:
3261 pass
3262 return False
3263 return self.changed_content(target, prev_ni)
3264
3270
3276
3304
3305 @SCons.Memoize.CountMethodCall
3337
3339 return str(self.rfile())
3340
3342 """
3343 Fetch a Node's content signature for purposes of computing
3344 another Node's cachesig.
3345
3346 This is a wrapper around the normal get_csig() method that handles
3347 the somewhat obscure case of using CacheDir with the -n option.
3348 Any files that don't exist would normally be "built" by fetching
3349 them from the cache, but the normal get_csig() method will try
3350 to open up the local file, which doesn't exist because the -n
3351 option meant we didn't actually pull the file from cachedir.
3352 But since the file *does* actually exist in the cachedir, we
3353 can use its contents for the csig.
3354 """
3355 try:
3356 return self.cachedir_csig
3357 except AttributeError:
3358 pass
3359
3360 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
3361 if not self.exists() and cachefile and os.path.exists(cachefile):
3362 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
3363 SCons.Node.FS.File.md5_chunksize * 1024)
3364 else:
3365 self.cachedir_csig = self.get_csig()
3366 return self.cachedir_csig
3367
3368 - def get_contents_sig(self):
3369 """
3370 A helper method for get_cachedir_bsig.
3371
3372 It computes and returns the signature for this
3373 node's contents.
3374 """
3375
3376 try:
3377 return self.contentsig
3378 except AttributeError:
3379 pass
3380
3381 executor = self.get_executor()
3382
3383 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
3384 return result
3385
3387 """
3388 Return the signature for a cached file, including
3389 its children.
3390
3391 It adds the path of the cached file to the cache signature,
3392 because multiple targets built by the same action will all
3393 have the same build signature, and we have to differentiate
3394 them somehow.
3395 """
3396 try:
3397 return self.cachesig
3398 except AttributeError:
3399 pass
3400
3401
3402 children = self.children()
3403 sigs = [n.get_cachedir_csig() for n in children]
3404
3405 sigs.append(self.get_contents_sig())
3406
3407 sigs.append(self.get_internal_path())
3408
3409 result = self.cachesig = SCons.Util.MD5collect(sigs)
3410 return result
3411
3412 default_fs = None
3419
3421 """
3422 """
3423
3426
3428 """
3429 A helper method for find_file() that looks up a directory for
3430 a file we're trying to find. This only creates the Dir Node if
3431 it exists on-disk, since if the directory doesn't exist we know
3432 we won't find any files in it... :-)
3433
3434 It would be more compact to just use this as a nested function
3435 with a default keyword argument (see the commented-out version
3436 below), but that doesn't work unless you have nested scopes,
3437 so we define it here just so this work under Python 1.5.2.
3438 """
3439 if fd is None:
3440 fd = self.default_filedir
3441 dir, name = os.path.split(fd)
3442 drive, d = _my_splitdrive(dir)
3443 if not name and d[:1] in ('/', OS_SEP):
3444
3445 return p.fs.get_root(drive)
3446 if dir:
3447 p = self.filedir_lookup(p, dir)
3448 if not p:
3449 return None
3450 norm_name = _my_normcase(name)
3451 try:
3452 node = p.entries[norm_name]
3453 except KeyError:
3454 return p.dir_on_disk(name)
3455 if isinstance(node, Dir):
3456 return node
3457 if isinstance(node, Entry):
3458 node.must_be_same(Dir)
3459 return node
3460 return None
3461
3463 return (filename, paths)
3464
3465 @SCons.Memoize.CountDictCall(_find_file_key)
3466 - def find_file(self, filename, paths, verbose=None):
3467 """
3468 find_file(str, [Dir()]) -> [nodes]
3469
3470 filename - a filename to find
3471 paths - a list of directory path *nodes* to search in. Can be
3472 represented as a list, a tuple, or a callable that is
3473 called with no arguments and returns the list or tuple.
3474
3475 returns - the node created from the found file.
3476
3477 Find a node corresponding to either a derived file or a file
3478 that exists already.
3479
3480 Only the first file found is returned, and none is returned
3481 if no file is found.
3482 """
3483 memo_key = self._find_file_key(filename, paths)
3484 try:
3485 memo_dict = self._memo['find_file']
3486 except KeyError:
3487 memo_dict = {}
3488 self._memo['find_file'] = memo_dict
3489 else:
3490 try:
3491 return memo_dict[memo_key]
3492 except KeyError:
3493 pass
3494
3495 if verbose and not callable(verbose):
3496 if not SCons.Util.is_String(verbose):
3497 verbose = "find_file"
3498 _verbose = u' %s: ' % verbose
3499 verbose = lambda s: sys.stdout.write(_verbose + s)
3500
3501 filedir, filename = os.path.split(filename)
3502 if filedir:
3503 self.default_filedir = filedir
3504 paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3505
3506 result = None
3507 for dir in paths:
3508 if verbose:
3509 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3510 node, d = dir.srcdir_find_file(filename)
3511 if node:
3512 if verbose:
3513 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3514 result = node
3515 break
3516
3517 memo_dict[memo_key] = result
3518
3519 return result
3520
3521 find_file = FileFinder().find_file
3525 """
3526 Invalidate the memoized values of all Nodes (files or directories)
3527 that are associated with the given entries. Has been added to
3528 clear the cache of nodes affected by a direct execution of an
3529 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3530 inconsistent if the action is run through Execute(). The argument
3531 `targets` can be a single Node object or filename, or a sequence
3532 of Nodes/filenames.
3533 """
3534 from traceback import extract_stack
3535
3536
3537
3538
3539
3540
3541 for f in extract_stack():
3542 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3543 break
3544 else:
3545
3546 return
3547
3548 if not SCons.Util.is_List(targets):
3549 targets = [targets]
3550
3551 for entry in targets:
3552
3553
3554 try:
3555 entry.clear_memoized_values()
3556 except AttributeError:
3557
3558
3559
3560 node = get_default_fs().Entry(entry)
3561 if node:
3562 node.clear_memoized_values()
3563
3564
3565
3566
3567
3568
3569