1 """scons.Node.FS
2
3 File system nodes.
4
5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
7
8 This holds a "default_fs" variable that should be initialized with an FS
9 that can be used by scripts or modules looking for the canonical default.
10
11 """
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 from __future__ import print_function
35
36 __revision__ = "src/engine/SCons/Node/FS.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
37
38 import fnmatch
39 import os
40 import re
41 import shutil
42 import stat
43 import sys
44 import time
45 import codecs
46
47 import SCons.Action
48 import SCons.Debug
49 from SCons.Debug import logInstanceCreation
50 import SCons.Errors
51 import SCons.Memoize
52 import SCons.Node
53 import SCons.Node.Alias
54 import SCons.Subst
55 import SCons.Util
56 import SCons.Warnings
57
58 from SCons.Debug import Trace
59
60 print_duplicate = 0
64 raise NotImplementedError
65
73
74 _sconsign_map = {0 : sconsign_none,
75 1 : sconsign_dir}
76
77 -class EntryProxyAttributeError(AttributeError):
78 """
79 An AttributeError subclass for recording and displaying the name
80 of the underlying Entry involved in an AttributeError exception.
81 """
82 - def __init__(self, entry_proxy, attribute):
83 AttributeError.__init__(self)
84 self.entry_proxy = entry_proxy
85 self.attribute = attribute
87 entry = self.entry_proxy.get()
88 fmt = "%s instance %s has no attribute %s"
89 return fmt % (entry.__class__.__name__,
90 repr(entry.name),
91 repr(self.attribute))
92
93
94
95 default_max_drift = 2*24*60*60
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 Save_Strings = None
120
121
122
123
124
125
126
127
128 do_splitdrive = None
129 _my_splitdrive =None
149 else:
150 def splitdrive(p):
151 if p[1:2] == ':':
152 return p[:2], p[2:]
153 return '', p
154 _my_splitdrive = splitdrive
155
156
157
158 global OS_SEP
159 global UNC_PREFIX
160 global os_sep_is_slash
161
162 OS_SEP = os.sep
163 UNC_PREFIX = OS_SEP + OS_SEP
164 os_sep_is_slash = OS_SEP == '/'
165
166 initialize_do_splitdrive()
167
168
169 needs_normpath_check = re.compile(
170 r'''
171 # We need to renormalize the path if it contains any consecutive
172 # '/' characters.
173 .*// |
174
175 # We need to renormalize the path if it contains a '..' directory.
176 # Note that we check for all the following cases:
177 #
178 # a) The path is a single '..'
179 # b) The path starts with '..'. E.g. '../' or '../moredirs'
180 # but we not match '..abc/'.
181 # c) The path ends with '..'. E.g. '/..' or 'dirs/..'
182 # d) The path contains a '..' in the middle.
183 # E.g. dirs/../moredirs
184
185 (.*/)?\.\.(?:/|$) |
186
187 # We need to renormalize the path if it contains a '.'
188 # directory, but NOT if it is a single '.' '/' characters. We
189 # do not want to match a single '.' because this case is checked
190 # for explicitly since this is common enough case.
191 #
192 # Note that we check for all the following cases:
193 #
194 # a) We don't match a single '.'
195 # b) We match if the path starts with '.'. E.g. './' or
196 # './moredirs' but we not match '.abc/'.
197 # c) We match if the path ends with '.'. E.g. '/.' or
198 # 'dirs/.'
199 # d) We match if the path contains a '.' in the middle.
200 # E.g. dirs/./moredirs
201
202 \./|.*/\.(?:/|$)
203
204 ''',
205 re.VERBOSE
206 )
207 needs_normpath_match = needs_normpath_check.match
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228 if hasattr(os, 'link') and sys.platform != 'win32':
241 else:
242 _hardlink_func = None
243
244 if hasattr(os, 'symlink') and sys.platform != 'win32':
247 else:
248 _softlink_func = None
254
255
256 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
257 'hard-copy', 'soft-copy', 'copy']
258
259 Link_Funcs = []
283
315
316 Link = SCons.Action.Action(LinkFunc, None)
318 return 'Local copy of %s from %s' % (target[0], source[0])
319
320 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
326
327 Unlink = SCons.Action.Action(UnlinkFunc, None)
334
335 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
336
337 MkdirBuilder = None
353
356
357 _null = _Null()
358
359
360 _is_cygwin = sys.platform == "cygwin"
361 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
364 else:
367
372 self.type = type
373 self.do = do
374 self.ignore = ignore
375 self.func = do
377 return self.func(*args, **kw)
378 - def set(self, list):
379 if self.type in list:
380 self.func = self.do
381 else:
382 self.func = self.ignore
383
385 result = predicate()
386 try:
387
388
389
390
391
392
393 if node._memo['stat'] is None:
394 del node._memo['stat']
395 except (AttributeError, KeyError):
396 pass
397 if result:
398 raise TypeError(errorfmt % node.get_abspath())
399
402
403
404
405 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
406
407 diskcheckers = [
408 diskcheck_match,
409 ]
414
417
418
419
420 -class EntryProxy(SCons.Util.Proxy):
421
422 __str__ = SCons.Util.Delegate('__str__')
423
424
425
426
427 __hash__ = SCons.Util.Delegate('__hash__')
428
429 - def __get_abspath(self):
430 entry = self.get()
431 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
432 entry.name + "_abspath")
433
434 - def __get_filebase(self):
438
439 - def __get_suffix(self):
443
444 - def __get_file(self):
447
448 - def __get_base_path(self):
449 """Return the file's directory and file name, with the
450 suffix stripped."""
451 entry = self.get()
452 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
453 entry.name + "_base")
454
456 """Return the path with / as the path separator,
457 regardless of platform."""
458 if os_sep_is_slash:
459 return self
460 else:
461 entry = self.get()
462 r = entry.get_path().replace(OS_SEP, '/')
463 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
464
466 """Return the path with \ as the path separator,
467 regardless of platform."""
468 if OS_SEP == '\\':
469 return self
470 else:
471 entry = self.get()
472 r = entry.get_path().replace(OS_SEP, '\\')
473 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
474
475 - def __get_srcnode(self):
476 return EntryProxy(self.get().srcnode())
477
478 - def __get_srcdir(self):
479 """Returns the directory containing the source node linked to this
480 node via VariantDir(), or the directory of this node if not linked."""
481 return EntryProxy(self.get().srcnode().dir)
482
483 - def __get_rsrcnode(self):
484 return EntryProxy(self.get().srcnode().rfile())
485
486 - def __get_rsrcdir(self):
487 """Returns the directory containing the source node linked to this
488 node via VariantDir(), or the directory of this node if not linked."""
489 return EntryProxy(self.get().srcnode().rfile().dir)
490
491 - def __get_dir(self):
492 return EntryProxy(self.get().dir)
493
494 dictSpecialAttrs = { "base" : __get_base_path,
495 "posix" : __get_posix_path,
496 "windows" : __get_windows_path,
497 "win32" : __get_windows_path,
498 "srcpath" : __get_srcnode,
499 "srcdir" : __get_srcdir,
500 "dir" : __get_dir,
501 "abspath" : __get_abspath,
502 "filebase" : __get_filebase,
503 "suffix" : __get_suffix,
504 "file" : __get_file,
505 "rsrcpath" : __get_rsrcnode,
506 "rsrcdir" : __get_rsrcdir,
507 }
508
509 - def __getattr__(self, name):
510
511
512 try:
513 attr_function = self.dictSpecialAttrs[name]
514 except KeyError:
515 try:
516 attr = SCons.Util.Proxy.__getattr__(self, name)
517 except AttributeError as e:
518
519
520
521 raise EntryProxyAttributeError(self, name)
522 return attr
523 else:
524 return attr_function(self)
525
526
527 -class Base(SCons.Node.Node):
528 """A generic class for file system entries. This class is for
529 when we don't know yet whether the entry being looked up is a file
530 or a directory. Instances of this class can morph into either
531 Dir or File objects by a later, more precise lookup.
532
533 Note: this class does not define __cmp__ and __hash__ for
534 efficiency reasons. SCons does a lot of comparing of
535 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
536 as fast as possible, which means we want to use Python's built-in
537 object identity comparisons.
538 """
539
540 __slots__ = ['name',
541 'fs',
542 '_abspath',
543 '_labspath',
544 '_path',
545 '_tpath',
546 '_path_elements',
547 'dir',
548 'cwd',
549 'duplicate',
550 '_local',
551 'sbuilder',
552 '_proxy',
553 '_func_sconsign']
554
555 - def __init__(self, name, directory, fs):
590
592 return '"' + self.__str__() + '"'
593
595 """
596 This node, which already existed, is being looked up as the
597 specified klass. Raise an exception if it isn't.
598 """
599 if isinstance(self, klass) or klass is Entry:
600 return
601 raise TypeError("Tried to lookup %s '%s' as a %s." %\
602 (self.__class__.__name__, self.get_internal_path(), klass.__name__))
603
606
609
612
614 """ Together with the node_bwcomp dict defined below,
615 this method provides a simple backward compatibility
616 layer for the Node attributes 'abspath', 'labspath',
617 'path', 'tpath', 'suffix' and 'path_elements'. These Node
618 attributes used to be directly available in v2.3 and earlier, but
619 have been replaced by getter methods that initialize the
620 single variables lazily when required, in order to save memory.
621 The redirection to the getters lets older Tools and
622 SConstruct continue to work without any additional changes,
623 fully transparent to the user.
624 Note, that __getattr__ is only called as fallback when the
625 requested attribute can't be found, so there should be no
626 speed performance penalty involved for standard builds.
627 """
628 if attr in node_bwcomp:
629 return node_bwcomp[attr](self)
630
631 raise AttributeError("%r object has no attribute %r" %
632 (self.__class__, attr))
633
641
643 """ less than operator used by sorting on py3"""
644 return str(self) < str(other)
645
646 @SCons.Memoize.CountMethodCall
655
680
681 rstr = __str__
682
683 @SCons.Memoize.CountMethodCall
691
694
697
699 st = self.stat()
700 if st: return st[stat.ST_MTIME]
701 else: return None
702
704 st = self.stat()
705 if st: return st[stat.ST_SIZE]
706 else: return None
707
709 st = self.stat()
710 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
711
713 st = self.stat()
714 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
715
716 if hasattr(os, 'symlink'):
721 else:
724
730
733
745
747 """Return path relative to the current working directory of the
748 Node.FS.Base object that owns us."""
749 if not dir:
750 dir = self.fs.getcwd()
751 if self == dir:
752 return '.'
753 path_elems = self.get_path_elements()
754 pathname = ''
755 try: i = path_elems.index(dir)
756 except ValueError:
757 for p in path_elems[:-1]:
758 pathname += p.dirname
759 else:
760 for p in path_elems[i+1:-1]:
761 pathname += p.dirname
762 return pathname + path_elems[-1].name
763
769
771 """Fetch the source code builder for this node.
772
773 If there isn't one, we cache the source code builder specified
774 for the directory (which in turn will cache the value from its
775 parent directory, and so on up to the file system root).
776 """
777 try:
778 scb = self.sbuilder
779 except AttributeError:
780 scb = self.dir.src_builder()
781 self.sbuilder = scb
782 return scb
783
787
791
797
803
806
808
809
810
811 return self.name
812
814 try:
815 return self._proxy
816 except AttributeError:
817 ret = EntryProxy(self)
818 self._proxy = ret
819 return ret
820
822 """
823
824 Generates a target entry that corresponds to this entry (usually
825 a source file) with the specified prefix and suffix.
826
827 Note that this method can be overridden dynamically for generated
828 files that need different behavior. See Tool/swig.py for
829 an example.
830 """
831 return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext)
832
835
836 @SCons.Memoize.CountDictCall(_Rfindalldirs_key)
838 """
839 Return all of the directories for a given path list, including
840 corresponding "backing" directories in any repositories.
841
842 The Node lookups are relative to this Node (typically a
843 directory), so memoizing result saves cycles from looking
844 up the same path for each target in a given directory.
845 """
846 try:
847 memo_dict = self._memo['Rfindalldirs']
848 except KeyError:
849 memo_dict = {}
850 self._memo['Rfindalldirs'] = memo_dict
851 else:
852 try:
853 return memo_dict[pathlist]
854 except KeyError:
855 pass
856
857 create_dir_relative_to_self = self.Dir
858 result = []
859 for path in pathlist:
860 if isinstance(path, SCons.Node.Node):
861 result.append(path)
862 else:
863 dir = create_dir_relative_to_self(path)
864 result.extend(dir.get_all_rdirs())
865
866 memo_dict[pathlist] = result
867
868 return result
869
870 - def RDirs(self, pathlist):
871 """Search for a list of directories in the Repository list."""
872 cwd = self.cwd or self.fs._cwd
873 return cwd.Rfindalldirs(pathlist)
874
875 @SCons.Memoize.CountMethodCall
877 try:
878 return self._memo['rentry']
879 except KeyError:
880 pass
881 result = self
882 if not self.exists():
883 norm_name = _my_normcase(self.name)
884 for dir in self.dir.get_all_rdirs():
885 try:
886 node = dir.entries[norm_name]
887 except KeyError:
888 if dir.entry_exists_on_disk(self.name):
889 result = dir.Entry(self.name)
890 break
891 self._memo['rentry'] = result
892 return result
893
894 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
896
897
898
899
900
901 node_bwcomp = {'abspath' : Base.get_abspath,
902 'labspath' : Base.get_labspath,
903 'path' : Base.get_internal_path,
904 'tpath' : Base.get_tpath,
905 'path_elements' : Base.get_path_elements,
906 'suffix' : Base.get_suffix}
907
908 -class Entry(Base):
909 """This is the class for generic Node.FS entries--that is, things
910 that could be a File or a Dir, but we're just not sure yet.
911 Consequently, the methods in this class really exist just to
912 transform their associated object into the right class when the
913 time comes, and then call the same-named method in the transformed
914 class."""
915
916 __slots__ = ['scanner_paths',
917 'cachedir_csig',
918 'cachesig',
919 'repositories',
920 'srcdir',
921 'entries',
922 'searched',
923 '_sconsign',
924 'variant_dirs',
925 'root',
926 'dirname',
927 'on_disk_entries',
928 'released_target_info',
929 'contentsig']
930
931 - def __init__(self, name, directory, fs):
932 Base.__init__(self, name, directory, fs)
933 self._func_exists = 3
934 self._func_get_contents = 1
935
936 - def diskcheck_match(self):
938
939 - def disambiguate(self, must_exist=None):
940 """
941 """
942 if self.isdir():
943 self.__class__ = Dir
944 self._morph()
945 elif self.isfile():
946 self.__class__ = File
947 self._morph()
948 self.clear()
949 else:
950
951
952
953
954
955
956
957
958
959 srcdir = self.dir.srcnode()
960 if srcdir != self.dir and \
961 srcdir.entry_exists_on_disk(self.name) and \
962 self.srcnode().isdir():
963 self.__class__ = Dir
964 self._morph()
965 elif must_exist:
966 msg = "No such file or directory: '%s'" % self.get_abspath()
967 raise SCons.Errors.UserError(msg)
968 else:
969 self.__class__ = File
970 self._morph()
971 self.clear()
972 return self
973
975 """We're a generic Entry, but the caller is actually looking for
976 a File at this point, so morph into one."""
977 self.__class__ = File
978 self._morph()
979 self.clear()
980 return File.rfile(self)
981
982 - def scanner_key(self):
983 return self.get_suffix()
984
985 - def get_contents(self):
986 """Fetch the contents of the entry. Returns the exact binary
987 contents of the file."""
988 return SCons.Node._get_contents_map[self._func_get_contents](self)
989
991 """Fetch the decoded text contents of a Unicode encoded Entry.
992
993 Since this should return the text contents from the file
994 system, we check to see into what sort of subclass we should
995 morph this Entry."""
996 try:
997 self = self.disambiguate(must_exist=1)
998 except SCons.Errors.UserError:
999
1000
1001
1002
1003
1004 return ''
1005 else:
1006 return self.get_text_contents()
1007
1008 - def must_be_same(self, klass):
1009 """Called to make sure a Node is a Dir. Since we're an
1010 Entry, we can morph into one."""
1011 if self.__class__ is not klass:
1012 self.__class__ = klass
1013 self._morph()
1014 self.clear()
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1029
1030 - def rel_path(self, other):
1031 d = self.disambiguate()
1032 if d.__class__ is Entry:
1033 raise Exception("rel_path() could not disambiguate File/Dir")
1034 return d.rel_path(other)
1035
1036 - def new_ninfo(self):
1037 return self.disambiguate().new_ninfo()
1038
1039 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1040 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1041
1042 - def get_subst_proxy(self):
1044
1045
1046
1047 _classEntry = Entry
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068 - def chmod(self, path, mode):
1070 - def copy(self, src, dst):
1071 return shutil.copy(src, dst)
1072 - def copy2(self, src, dst):
1073 return shutil.copy2(src, dst)
1084 - def link(self, src, dst):
1085 return os.link(src, dst)
1095 return os.rename(old, new)
1096 - def stat(self, path):
1100 - def open(self, path):
1104
1105 if hasattr(os, 'symlink'):
1108 else:
1111
1112 if hasattr(os, 'readlink'):
1115 else:
1118
1119
1120 -class FS(LocalFS):
1121
1123 """Initialize the Node.FS subsystem.
1124
1125 The supplied path is the top of the source tree, where we
1126 expect to find the top-level build file. If no path is
1127 supplied, the current directory is the default.
1128
1129 The path argument must be a valid absolute path.
1130 """
1131 if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS')
1132
1133 self._memo = {}
1134
1135 self.Root = {}
1136 self.SConstruct_dir = None
1137 self.max_drift = default_max_drift
1138
1139 self.Top = None
1140 if path is None:
1141 self.pathTop = os.getcwd()
1142 else:
1143 self.pathTop = path
1144 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1145
1146 self.Top = self.Dir(self.pathTop)
1147 self.Top._path = '.'
1148 self.Top._tpath = '.'
1149 self._cwd = self.Top
1150
1151 DirNodeInfo.fs = self
1152 FileNodeInfo.fs = self
1153
1155 self.SConstruct_dir = dir
1156
1158 return self.max_drift
1159
1161 self.max_drift = max_drift
1162
1164 if hasattr(self, "_cwd"):
1165 return self._cwd
1166 else:
1167 return "<no cwd>"
1168
1169 - def chdir(self, dir, change_os_dir=0):
1170 """Change the current working directory for lookups.
1171 If change_os_dir is true, we will also change the "real" cwd
1172 to match.
1173 """
1174 curr=self._cwd
1175 try:
1176 if dir is not None:
1177 self._cwd = dir
1178 if change_os_dir:
1179 os.chdir(dir.get_abspath())
1180 except OSError:
1181 self._cwd = curr
1182 raise
1183
1185 """
1186 Returns the root directory for the specified drive, creating
1187 it if necessary.
1188 """
1189 drive = _my_normcase(drive)
1190 try:
1191 return self.Root[drive]
1192 except KeyError:
1193 root = RootDir(drive, self)
1194 self.Root[drive] = root
1195 if not drive:
1196 self.Root[self.defaultDrive] = root
1197 elif drive == self.defaultDrive:
1198 self.Root[''] = root
1199 return root
1200
1201 - def _lookup(self, p, directory, fsclass, create=1):
1202 """
1203 The generic entry point for Node lookup with user-supplied data.
1204
1205 This translates arbitrary input into a canonical Node.FS object
1206 of the specified fsclass. The general approach for strings is
1207 to turn it into a fully normalized absolute path and then call
1208 the root directory's lookup_abs() method for the heavy lifting.
1209
1210 If the path name begins with '#', it is unconditionally
1211 interpreted relative to the top-level directory of this FS. '#'
1212 is treated as a synonym for the top-level SConstruct directory,
1213 much like '~' is treated as a synonym for the user's home
1214 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1215 to the 'foo' subdirectory underneath the top-level SConstruct
1216 directory.
1217
1218 If the path name is relative, then the path is looked up relative
1219 to the specified directory, or the current directory (self._cwd,
1220 typically the SConscript directory) if the specified directory
1221 is None.
1222 """
1223 if isinstance(p, Base):
1224
1225
1226 p.must_be_same(fsclass)
1227 return p
1228
1229 p = str(p)
1230
1231 if not os_sep_is_slash:
1232 p = p.replace(OS_SEP, '/')
1233
1234 if p[0:1] == '#':
1235
1236
1237
1238 p = p[1:]
1239 directory = self.Top
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251 if do_splitdrive:
1252 drive, p = _my_splitdrive(p)
1253 if drive:
1254 root = self.get_root(drive)
1255 else:
1256 root = directory.root
1257 else:
1258 root = directory.root
1259
1260
1261
1262 p = p.strip('/')
1263
1264 needs_normpath = needs_normpath_match(p)
1265
1266
1267 if p in ('', '.'):
1268 p = directory.get_labspath()
1269 else:
1270 p = directory.get_labspath() + '/' + p
1271 else:
1272 if do_splitdrive:
1273 drive, p = _my_splitdrive(p)
1274 if drive and not p:
1275
1276
1277
1278 p = '/'
1279 else:
1280 drive = ''
1281
1282
1283
1284 if p != '/':
1285 p = p.rstrip('/')
1286
1287 needs_normpath = needs_normpath_match(p)
1288
1289 if p[0:1] == '/':
1290
1291 root = self.get_root(drive)
1292 else:
1293
1294
1295
1296
1297 if directory:
1298 if not isinstance(directory, Dir):
1299 directory = self.Dir(directory)
1300 else:
1301 directory = self._cwd
1302
1303 if p in ('', '.'):
1304 p = directory.get_labspath()
1305 else:
1306 p = directory.get_labspath() + '/' + p
1307
1308 if drive:
1309 root = self.get_root(drive)
1310 else:
1311 root = directory.root
1312
1313 if needs_normpath is not None:
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323 ins = p.split('/')[1:]
1324 outs = []
1325 for d in ins:
1326 if d == '..':
1327 try:
1328 outs.pop()
1329 except IndexError:
1330 pass
1331 elif d not in ('', '.'):
1332 outs.append(d)
1333 p = '/' + '/'.join(outs)
1334
1335 return root._lookup_abs(p, fsclass, create)
1336
1337 - def Entry(self, name, directory = None, create = 1):
1338 """Look up or create a generic Entry node with the specified name.
1339 If the name is a relative path (begins with ./, ../, or a file
1340 name), then it is looked up relative to the supplied directory
1341 node, or to the top level directory of the FS (supplied at
1342 construction time) if no directory is supplied.
1343 """
1344 return self._lookup(name, directory, Entry, create)
1345
1346 - def File(self, name, directory = None, create = 1):
1347 """Look up or create a File node with the specified name. If
1348 the name is a relative path (begins with ./, ../, or a file name),
1349 then it is looked up relative to the supplied directory node,
1350 or to the top level directory of the FS (supplied at construction
1351 time) if no directory is supplied.
1352
1353 This method will raise TypeError if a directory is found at the
1354 specified path.
1355 """
1356 return self._lookup(name, directory, File, create)
1357
1358 - def Dir(self, name, directory = None, create = True):
1359 """Look up or create a Dir node with the specified name. If
1360 the name is a relative path (begins with ./, ../, or a file name),
1361 then it is looked up relative to the supplied directory node,
1362 or to the top level directory of the FS (supplied at construction
1363 time) if no directory is supplied.
1364
1365 This method will raise TypeError if a normal file is found at the
1366 specified path.
1367 """
1368 return self._lookup(name, directory, Dir, create)
1369
1370 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1371 """Link the supplied variant directory to the source directory
1372 for purposes of building files."""
1373
1374 if not isinstance(src_dir, SCons.Node.Node):
1375 src_dir = self.Dir(src_dir)
1376 if not isinstance(variant_dir, SCons.Node.Node):
1377 variant_dir = self.Dir(variant_dir)
1378 if src_dir.is_under(variant_dir):
1379 raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
1380 if variant_dir.srcdir:
1381 if variant_dir.srcdir == src_dir:
1382 return
1383 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
1384 variant_dir.link(src_dir, duplicate)
1385
1392
1394 """Locate the directory of a given python module name
1395
1396 For example scons might resolve to
1397 Windows: C:\Python27\Lib\site-packages\scons-2.5.1
1398 Linux: /usr/lib/scons
1399
1400 This can be useful when we want to determine a toolpath based on a python module name"""
1401
1402 dirpath = ''
1403 if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] in (0,1,2,3,4)):
1404
1405 import imp
1406 splitname = modulename.split('.')
1407 srchpths = sys.path
1408 for item in splitname:
1409 file, path, desc = imp.find_module(item, srchpths)
1410 if file is not None:
1411 path = os.path.dirname(path)
1412 srchpths = [path]
1413 dirpath = path
1414 else:
1415
1416 import importlib.util
1417 modspec = importlib.util.find_spec(modulename)
1418 dirpath = os.path.dirname(modspec.origin)
1419 return self._lookup(dirpath, None, Dir, True)
1420
1421
1423 """Create targets in corresponding variant directories
1424
1425 Climb the directory tree, and look up path names
1426 relative to any linked variant directories we find.
1427
1428 Even though this loops and walks up the tree, we don't memoize
1429 the return value because this is really only used to process
1430 the command-line targets.
1431 """
1432 targets = []
1433 message = None
1434 fmt = "building associated VariantDir targets: %s"
1435 start_dir = dir
1436 while dir:
1437 for bd in dir.variant_dirs:
1438 if start_dir.is_under(bd):
1439
1440 return [orig], fmt % str(orig)
1441 p = os.path.join(bd._path, *tail)
1442 targets.append(self.Entry(p))
1443 tail = [dir.name] + tail
1444 dir = dir.up()
1445 if targets:
1446 message = fmt % ' '.join(map(str, targets))
1447 return targets, message
1448
1449 - def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
1450 """
1451 Globs
1452
1453 This is mainly a shim layer
1454 """
1455 if cwd is None:
1456 cwd = self.getcwd()
1457 return cwd.glob(pathname, ondisk, source, strings, exclude)
1458
1476
1480
1481 glob_magic_check = re.compile('[*?[]')
1485
1487 """A class for directories in a file system.
1488 """
1489
1490 __slots__ = ['scanner_paths',
1491 'cachedir_csig',
1492 'cachesig',
1493 'repositories',
1494 'srcdir',
1495 'entries',
1496 'searched',
1497 '_sconsign',
1498 'variant_dirs',
1499 'root',
1500 'dirname',
1501 'on_disk_entries',
1502 'released_target_info',
1503 'contentsig']
1504
1505 NodeInfo = DirNodeInfo
1506 BuildInfo = DirBuildInfo
1507
1508 - def __init__(self, name, directory, fs):
1512
1578
1582
1584 """Called when we change the repository(ies) for a directory.
1585 This clears any cached information that is invalidated by changing
1586 the repository."""
1587
1588 for node in list(self.entries.values()):
1589 if node != self.dir:
1590 if node != self and isinstance(node, Dir):
1591 node.__clearRepositoryCache(duplicate)
1592 else:
1593 node.clear()
1594 try:
1595 del node._srcreps
1596 except AttributeError:
1597 pass
1598 if duplicate is not None:
1599 node.duplicate=duplicate
1600
1604
1605 - def Entry(self, name):
1606 """
1607 Looks up or creates an entry node named 'name' relative to
1608 this directory.
1609 """
1610 return self.fs.Entry(name, self)
1611
1612 - def Dir(self, name, create=True):
1613 """
1614 Looks up or creates a directory node named 'name' relative to
1615 this directory.
1616 """
1617 return self.fs.Dir(name, self, create)
1618
1619 - def File(self, name):
1620 """
1621 Looks up or creates a file node named 'name' relative to
1622 this directory.
1623 """
1624 return self.fs.File(name, self)
1625
1626 - def link(self, srcdir, duplicate):
1633
1640
1641 @SCons.Memoize.CountMethodCall
1663
1669
1672
1675
1676 @SCons.Memoize.CountDictCall(_rel_path_key)
1678 """Return a path to "other" relative to this directory.
1679 """
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691 try:
1692 memo_dict = self._memo['rel_path']
1693 except KeyError:
1694 memo_dict = {}
1695 self._memo['rel_path'] = memo_dict
1696 else:
1697 try:
1698 return memo_dict[other]
1699 except KeyError:
1700 pass
1701
1702 if self is other:
1703 result = '.'
1704
1705 elif not other in self._path_elements:
1706 try:
1707 other_dir = other.get_dir()
1708 except AttributeError:
1709 result = str(other)
1710 else:
1711 if other_dir is None:
1712 result = other.name
1713 else:
1714 dir_rel_path = self.rel_path(other_dir)
1715 if dir_rel_path == '.':
1716 result = other.name
1717 else:
1718 result = dir_rel_path + OS_SEP + other.name
1719 else:
1720 i = self._path_elements.index(other) + 1
1721
1722 path_elems = ['..'] * (len(self._path_elements) - i) \
1723 + [n.name for n in other._path_elements[i:]]
1724
1725 result = OS_SEP.join(path_elems)
1726
1727 memo_dict[other] = result
1728
1729 return result
1730
1734
1738
1740 """Return this directory's implicit dependencies.
1741
1742 We don't bother caching the results because the scan typically
1743 shouldn't be requested more than once (as opposed to scanning
1744 .h file contents, which can be requested as many times as the
1745 files is #included by other files).
1746 """
1747 if not scanner:
1748 return []
1749
1750
1751
1752
1753
1754
1755
1756
1757 self.clear()
1758 return scanner(self, env, path)
1759
1760
1761
1762
1763
1766
1772
1773
1774
1775
1776
1778 """Create this directory, silently and without worrying about
1779 whether the builder is the default or not."""
1780 listDirs = []
1781 parent = self
1782 while parent:
1783 if parent.exists():
1784 break
1785 listDirs.append(parent)
1786 p = parent.up()
1787 if p is None:
1788
1789
1790 raise SCons.Errors.StopError(parent._path)
1791 parent = p
1792 listDirs.reverse()
1793 for dirnode in listDirs:
1794 try:
1795
1796
1797
1798
1799 SCons.Node.Node.build(dirnode)
1800 dirnode.get_executor().nullify()
1801
1802
1803
1804
1805 dirnode.clear()
1806 except OSError:
1807 pass
1808
1812
1814 """Return any corresponding targets in a variant directory.
1815 """
1816 return self.fs.variant_dir_target_climb(self, self, [])
1817
1819 """A directory does not get scanned."""
1820 return None
1821
1823 """We already emit things in text, so just return the binary
1824 version."""
1825 return self.get_contents()
1826
1827 - def get_contents(self):
1828 """Return content signatures and names of all our children
1829 separated by new-lines. Ensure that the nodes are sorted."""
1830 return SCons.Node._get_contents_map[self._func_get_contents](self)
1831
1833 """Compute the content signature for Directory nodes. In
1834 general, this is not needed and the content signature is not
1835 stored in the DirNodeInfo. However, if get_contents on a Dir
1836 node is called which has a child directory, the child
1837 directory should return the hash of its contents."""
1838 contents = self.get_contents()
1839 return SCons.Util.MD5signature(contents)
1840
1843
1854
1865
1869
1871 """Dir has a special need for srcnode()...if we
1872 have a srcdir attribute set, then that *is* our srcnode."""
1873 if self.srcdir:
1874 return self.srcdir
1875 return Base.srcnode(self)
1876
1878 """Return the latest timestamp from among our children"""
1879 stamp = 0
1880 for kid in self.children():
1881 if kid.get_timestamp() > stamp:
1882 stamp = kid.get_timestamp()
1883 return stamp
1884
1886 """Get the absolute path of the file."""
1887 return self._abspath
1888
1890 """Get the absolute path of the file."""
1891 return self._labspath
1892
1895
1898
1901
1902 - def entry_abspath(self, name):
1903 return self._abspath + OS_SEP + name
1904
1905 - def entry_labspath(self, name):
1906 return self._labspath + '/' + name
1907
1908 - def entry_path(self, name):
1909 return self._path + OS_SEP + name
1910
1911 - def entry_tpath(self, name):
1912 return self._tpath + OS_SEP + name
1913
1914 - def entry_exists_on_disk(self, name):
1915 """ Searches through the file/dir entries of the current
1916 directory, and returns True if a physical entry with the given
1917 name could be found.
1918
1919 @see rentry_exists_on_disk
1920 """
1921 try:
1922 d = self.on_disk_entries
1923 except AttributeError:
1924 d = {}
1925 try:
1926 entries = os.listdir(self._abspath)
1927 except OSError:
1928 pass
1929 else:
1930 for entry in map(_my_normcase, entries):
1931 d[entry] = True
1932 self.on_disk_entries = d
1933 if sys.platform == 'win32' or sys.platform == 'cygwin':
1934 name = _my_normcase(name)
1935 result = d.get(name)
1936 if result is None:
1937
1938
1939 result = os.path.exists(self._abspath + OS_SEP + name)
1940 d[name] = result
1941 return result
1942 else:
1943 return name in d
1944
1945 - def rentry_exists_on_disk(self, name):
1946 """ Searches through the file/dir entries of the current
1947 *and* all its remote directories (repos), and returns
1948 True if a physical entry with the given name could be found.
1949 The local directory (self) gets searched first, so
1950 repositories take a lower precedence regarding the
1951 searching order.
1952
1953 @see entry_exists_on_disk
1954 """
1955
1956 rentry_exists = self.entry_exists_on_disk(name)
1957 if not rentry_exists:
1958
1959 norm_name = _my_normcase(name)
1960 for rdir in self.get_all_rdirs():
1961 try:
1962 node = rdir.entries[norm_name]
1963 if node:
1964 rentry_exists = True
1965 break
1966 except KeyError:
1967 if rdir.entry_exists_on_disk(name):
1968 rentry_exists = True
1969 break
1970 return rentry_exists
1971
1972 @SCons.Memoize.CountMethodCall
1992
2009
2012
2013 @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
2015 try:
2016 memo_dict = self._memo['srcdir_find_file']
2017 except KeyError:
2018 memo_dict = {}
2019 self._memo['srcdir_find_file'] = memo_dict
2020 else:
2021 try:
2022 return memo_dict[filename]
2023 except KeyError:
2024 pass
2025
2026 def func(node):
2027 if (isinstance(node, File) or isinstance(node, Entry)) and \
2028 (node.is_derived() or node.exists()):
2029 return node
2030 return None
2031
2032 norm_name = _my_normcase(filename)
2033
2034 for rdir in self.get_all_rdirs():
2035 try: node = rdir.entries[norm_name]
2036 except KeyError: node = rdir.file_on_disk(filename)
2037 else: node = func(node)
2038 if node:
2039 result = (node, self)
2040 memo_dict[filename] = result
2041 return result
2042
2043 for srcdir in self.srcdir_list():
2044 for rdir in srcdir.get_all_rdirs():
2045 try: node = rdir.entries[norm_name]
2046 except KeyError: node = rdir.file_on_disk(filename)
2047 else: node = func(node)
2048 if node:
2049 result = (File(filename, self, self.fs), srcdir)
2050 memo_dict[filename] = result
2051 return result
2052
2053 result = (None, None)
2054 memo_dict[filename] = result
2055 return result
2056
2065
2074
2075 - def walk(self, func, arg):
2076 """
2077 Walk this directory tree by calling the specified function
2078 for each directory in the tree.
2079
2080 This behaves like the os.path.walk() function, but for in-memory
2081 Node.FS.Dir objects. The function takes the same arguments as
2082 the functions passed to os.path.walk():
2083
2084 func(arg, dirname, fnames)
2085
2086 Except that "dirname" will actually be the directory *Node*,
2087 not the string. The '.' and '..' entries are excluded from
2088 fnames. The fnames list may be modified in-place to filter the
2089 subdirectories visited or otherwise impose a specific order.
2090 The "arg" argument is always passed to func() and may be used
2091 in any way (or ignored, passing None is common).
2092 """
2093 entries = self.entries
2094 names = list(entries.keys())
2095 names.remove('.')
2096 names.remove('..')
2097 func(arg, self, names)
2098 for dirname in [n for n in names if isinstance(entries[n], Dir)]:
2099 entries[dirname].walk(func, arg)
2100
2101 - def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
2102 """
2103 Returns a list of Nodes (or strings) matching a specified
2104 pathname pattern.
2105
2106 Pathname patterns follow UNIX shell semantics: * matches
2107 any-length strings of any characters, ? matches any character,
2108 and [] can enclose lists or ranges of characters. Matches do
2109 not span directory separators.
2110
2111 The matches take into account Repositories, returning local
2112 Nodes if a corresponding entry exists in a Repository (either
2113 an in-memory Node or something on disk).
2114
2115 By defafult, the glob() function matches entries that exist
2116 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
2117 argument to False (or some other non-true value) causes the glob()
2118 function to only match in-memory Nodes. The default behavior is
2119 to return both the on-disk and in-memory Nodes.
2120
2121 The "source" argument, when true, specifies that corresponding
2122 source Nodes must be returned if you're globbing in a build
2123 directory (initialized with VariantDir()). The default behavior
2124 is to return Nodes local to the VariantDir().
2125
2126 The "strings" argument, when true, returns the matches as strings,
2127 not Nodes. The strings are path names relative to this directory.
2128
2129 The "exclude" argument, if not None, must be a pattern or a list
2130 of patterns following the same UNIX shell semantics.
2131 Elements matching a least one pattern of this list will be excluded
2132 from the result.
2133
2134 The underlying algorithm is adapted from the glob.glob() function
2135 in the Python library (but heavily modified), and uses fnmatch()
2136 under the covers.
2137 """
2138 dirname, basename = os.path.split(pathname)
2139 if not dirname:
2140 result = self._glob1(basename, ondisk, source, strings)
2141 else:
2142 if has_glob_magic(dirname):
2143 list = self.glob(dirname, ondisk, source, False, exclude)
2144 else:
2145 list = [self.Dir(dirname, create=True)]
2146 result = []
2147 for dir in list:
2148 r = dir._glob1(basename, ondisk, source, strings)
2149 if strings:
2150 r = [os.path.join(str(dir), x) for x in r]
2151 result.extend(r)
2152 if exclude:
2153 excludes = []
2154 excludeList = SCons.Util.flatten(exclude)
2155 for x in excludeList:
2156 r = self.glob(x, ondisk, source, strings)
2157 excludes.extend(r)
2158 result = [x for x in result if not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes))]
2159 return sorted(result, key=lambda a: str(a))
2160
2161 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2162 """
2163 Globs for and returns a list of entry names matching a single
2164 pattern in this directory.
2165
2166 This searches any repositories and source directories for
2167 corresponding entries and returns a Node (or string) relative
2168 to the current directory if an entry is found anywhere.
2169
2170 TODO: handle pattern with no wildcard
2171 """
2172 search_dir_list = self.get_all_rdirs()
2173 for srcdir in self.srcdir_list():
2174 search_dir_list.extend(srcdir.get_all_rdirs())
2175
2176 selfEntry = self.Entry
2177 names = []
2178 for dir in search_dir_list:
2179
2180
2181
2182 node_names = [ v.name for k, v in dir.entries.items()
2183 if k not in ('.', '..') ]
2184 names.extend(node_names)
2185 if not strings:
2186
2187
2188 for name in node_names: selfEntry(name)
2189 if ondisk:
2190 try:
2191 disk_names = os.listdir(dir._abspath)
2192 except os.error:
2193 continue
2194 names.extend(disk_names)
2195 if not strings:
2196
2197
2198
2199
2200
2201
2202
2203
2204 if pattern[0] != '.':
2205 disk_names = [x for x in disk_names if x[0] != '.']
2206 disk_names = fnmatch.filter(disk_names, pattern)
2207 dirEntry = dir.Entry
2208 for name in disk_names:
2209
2210
2211 name = './' + name
2212 node = dirEntry(name).disambiguate()
2213 n = selfEntry(name)
2214 if n.__class__ != node.__class__:
2215 n.__class__ = node.__class__
2216 n._morph()
2217
2218 names = set(names)
2219 if pattern[0] != '.':
2220 names = [x for x in names if x[0] != '.']
2221 names = fnmatch.filter(names, pattern)
2222
2223 if strings:
2224 return names
2225
2226 return [self.entries[_my_normcase(n)] for n in names]
2227
2229 """A class for the root directory of a file system.
2230
2231 This is the same as a Dir class, except that the path separator
2232 ('/' or '\\') is actually part of the name, so we don't need to
2233 add a separator when creating the path names of entries within
2234 this directory.
2235 """
2236
2237 __slots__ = ['_lookupDict']
2238
2292
2334
2335
2340
2342 """
2343 Fast (?) lookup of a *normalized* absolute path.
2344
2345 This method is intended for use by internal lookups with
2346 already-normalized path data. For general-purpose lookups,
2347 use the FS.Entry(), FS.Dir() or FS.File() methods.
2348
2349 The caller is responsible for making sure we're passed a
2350 normalized absolute path; we merely let Python's dictionary look
2351 up and return the One True Node.FS object for the path.
2352
2353 If a Node for the specified "p" doesn't already exist, and
2354 "create" is specified, the Node may be created after recursive
2355 invocation to find or create the parent directory or directories.
2356 """
2357 k = _my_normcase(p)
2358 try:
2359 result = self._lookupDict[k]
2360 except KeyError:
2361 if not create:
2362 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2363 raise SCons.Errors.UserError(msg)
2364
2365
2366 dir_name, file_name = p.rsplit('/',1)
2367 dir_node = self._lookup_abs(dir_name, Dir)
2368 result = klass(file_name, dir_node, self.fs)
2369
2370
2371
2372 result.diskcheck_match()
2373
2374 self._lookupDict[k] = result
2375 dir_node.entries[_my_normcase(file_name)] = result
2376 dir_node.implicit = None
2377 else:
2378
2379
2380 result.must_be_same(klass)
2381 return result
2382
2385
2386 - def entry_abspath(self, name):
2387 return self._abspath + name
2388
2389 - def entry_labspath(self, name):
2391
2392 - def entry_path(self, name):
2393 return self._path + name
2394
2395 - def entry_tpath(self, name):
2396 return self._tpath + name
2397
2399 if self is dir:
2400 return 1
2401 else:
2402 return 0
2403
2406
2409
2412
2415 __slots__ = ('csig', 'timestamp', 'size')
2416 current_version_id = 2
2417
2418 field_list = ['csig', 'timestamp', 'size']
2419
2420
2421 fs = None
2422
2433
2435 """
2436 Return all fields that shall be pickled. Walk the slots in the class
2437 hierarchy and add those to the state dictionary. If a '__dict__' slot is
2438 available, copy all entries to the dictionary. Also include the version
2439 id, which is fixed for all instances of a class.
2440 """
2441 state = getattr(self, '__dict__', {}).copy()
2442 for obj in type(self).mro():
2443 for name in getattr(obj,'__slots__',()):
2444 if hasattr(self, name):
2445 state[name] = getattr(self, name)
2446
2447 state['_version_id'] = self.current_version_id
2448 try:
2449 del state['__weakref__']
2450 except KeyError:
2451 pass
2452
2453 return state
2454
2456 """
2457 Restore the attributes from a pickled state.
2458 """
2459
2460 del state['_version_id']
2461 for key, value in state.items():
2462 if key not in ('__weakref__',):
2463 setattr(self, key, value)
2464
2467 __slots__ = ()
2468 current_version_id = 2
2469
2471 """
2472 Converts this FileBuildInfo object for writing to a .sconsign file
2473
2474 This replaces each Node in our various dependency lists with its
2475 usual string representation: relative to the top-level SConstruct
2476 directory, or an absolute path if it's outside.
2477 """
2478 if os_sep_is_slash:
2479 node_to_str = str
2480 else:
2481 def node_to_str(n):
2482 try:
2483 s = n.get_internal_path()
2484 except AttributeError:
2485 s = str(n)
2486 else:
2487 s = s.replace(OS_SEP, '/')
2488 return s
2489 for attr in ['bsources', 'bdepends', 'bimplicit']:
2490 try:
2491 val = getattr(self, attr)
2492 except AttributeError:
2493 pass
2494 else:
2495 setattr(self, attr, list(map(node_to_str, val)))
2496
2498 """
2499 Converts a newly-read FileBuildInfo object for in-SCons use
2500
2501 For normal up-to-date checking, we don't have any conversion to
2502 perform--but we're leaving this method here to make that clear.
2503 """
2504 pass
2505
2507 """
2508 Prepares a FileBuildInfo object for explaining what changed
2509
2510 The bsources, bdepends and bimplicit lists have all been
2511 stored on disk as paths relative to the top-level SConstruct
2512 directory. Convert the strings to actual Nodes (for use by the
2513 --debug=explain code and --implicit-cache).
2514 """
2515 attrs = [
2516 ('bsources', 'bsourcesigs'),
2517 ('bdepends', 'bdependsigs'),
2518 ('bimplicit', 'bimplicitsigs'),
2519 ]
2520 for (nattr, sattr) in attrs:
2521 try:
2522 strings = getattr(self, nattr)
2523 nodeinfos = getattr(self, sattr)
2524 except AttributeError:
2525 continue
2526 if strings is None or nodeinfos is None:
2527 continue
2528 nodes = []
2529 for s, ni in zip(strings, nodeinfos):
2530 if not isinstance(s, SCons.Node.Node):
2531 s = ni.str_to_node(s)
2532 nodes.append(s)
2533 setattr(self, nattr, nodes)
2534
2546
2547
2548 -class File(Base):
2549 """A class for files in a file system.
2550 """
2551
2552 __slots__ = ['scanner_paths',
2553 'cachedir_csig',
2554 'cachesig',
2555 'repositories',
2556 'srcdir',
2557 'entries',
2558 'searched',
2559 '_sconsign',
2560 'variant_dirs',
2561 'root',
2562 'dirname',
2563 'on_disk_entries',
2564 'released_target_info',
2565 'contentsig']
2566
2567 NodeInfo = FileNodeInfo
2568 BuildInfo = FileBuildInfo
2569
2570 md5_chunksize = 64
2571
2575
2576 - def __init__(self, name, directory, fs):
2580
2581 - def Entry(self, name):
2582 """Create an entry node named 'name' relative to
2583 the directory of this file."""
2584 return self.dir.Entry(name)
2585
2586 - def Dir(self, name, create=True):
2587 """Create a directory node named 'name' relative to
2588 the directory of this file."""
2589 return self.dir.Dir(name, create=create)
2590
2591 - def Dirs(self, pathlist):
2592 """Create a list of directories relative to the SConscript
2593 directory of this file."""
2594 return [self.Dir(p) for p in pathlist]
2595
2596 - def File(self, name):
2597 """Create a file node named 'name' relative to
2598 the directory of this file."""
2599 return self.dir.File(name)
2600
2629
2632
2633 - def get_contents(self):
2635
2637 """
2638 This attempts to figure out what the encoding of the text is
2639 based upon the BOM bytes, and then decodes the contents so that
2640 it's a valid python string.
2641 """
2642 contents = self.get_contents()
2643
2644
2645
2646
2647
2648
2649 if contents[:len(codecs.BOM_UTF8)] == codecs.BOM_UTF8:
2650 return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2651 if contents[:len(codecs.BOM_UTF16_LE)] == codecs.BOM_UTF16_LE:
2652 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2653 if contents[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
2654 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2655 try:
2656 return contents.decode('utf-8')
2657 except UnicodeDecodeError as e:
2658 try:
2659 return contents.decode('latin-1')
2660 except UnicodeDecodeError as e:
2661 return contents.decode('utf-8', error='backslashreplace')
2662
2663
2664 - def get_content_hash(self):
2665 """
2666 Compute and return the MD5 hash for this file.
2667 """
2668 if not self.rexists():
2669 return SCons.Util.MD5signature('')
2670 fname = self.rfile().get_abspath()
2671 try:
2672 cs = SCons.Util.MD5filesignature(fname,
2673 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2674 except EnvironmentError as e:
2675 if not e.filename:
2676 e.filename = fname
2677 raise
2678 return cs
2679
2680 @SCons.Memoize.CountMethodCall
2682 try:
2683 return self._memo['get_size']
2684 except KeyError:
2685 pass
2686
2687 if self.rexists():
2688 size = self.rfile().getsize()
2689 else:
2690 size = 0
2691
2692 self._memo['get_size'] = size
2693
2694 return size
2695
2696 @SCons.Memoize.CountMethodCall
2711
2712 convert_copy_attrs = [
2713 'bsources',
2714 'bimplicit',
2715 'bdepends',
2716 'bact',
2717 'bactsig',
2718 'ninfo',
2719 ]
2720
2721
2722 convert_sig_attrs = [
2723 'bsourcesigs',
2724 'bimplicitsigs',
2725 'bdependsigs',
2726 ]
2727
2728 - def convert_old_entry(self, old_entry):
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
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 import SCons.SConsign
2797 new_entry = SCons.SConsign.SConsignEntry()
2798 new_entry.binfo = self.new_binfo()
2799 binfo = new_entry.binfo
2800 for attr in self.convert_copy_attrs:
2801 try:
2802 value = getattr(old_entry, attr)
2803 except AttributeError:
2804 continue
2805 setattr(binfo, attr, value)
2806 delattr(old_entry, attr)
2807 for attr in self.convert_sig_attrs:
2808 try:
2809 sig_list = getattr(old_entry, attr)
2810 except AttributeError:
2811 continue
2812 value = []
2813 for sig in sig_list:
2814 ninfo = self.new_ninfo()
2815 if len(sig) == 32:
2816 ninfo.csig = sig
2817 else:
2818 ninfo.timestamp = sig
2819 value.append(ninfo)
2820 setattr(binfo, attr, value)
2821 delattr(old_entry, attr)
2822 return new_entry
2823
2824 @SCons.Memoize.CountMethodCall
2851
2857
2860
2862 return (id(env), id(scanner), path)
2863
2864 @SCons.Memoize.CountDictCall(_get_found_includes_key)
2866 """Return the included implicit dependencies in this file.
2867 Cache results so we only scan the file once per path
2868 regardless of how many times this information is requested.
2869 """
2870 memo_key = (id(env), id(scanner), path)
2871 try:
2872 memo_dict = self._memo['get_found_includes']
2873 except KeyError:
2874 memo_dict = {}
2875 self._memo['get_found_includes'] = memo_dict
2876 else:
2877 try:
2878 return memo_dict[memo_key]
2879 except KeyError:
2880 pass
2881
2882 if scanner:
2883 result = [n.disambiguate() for n in scanner(self, env, path)]
2884 else:
2885 result = []
2886
2887 memo_dict[memo_key] = result
2888
2889 return result
2890
2895
2911
2913 """Try to retrieve the node's content from a cache
2914
2915 This method is called from multiple threads in a parallel build,
2916 so only do thread safe stuff here. Do thread unsafe stuff in
2917 built().
2918
2919 Returns true if the node was successfully retrieved.
2920 """
2921 if self.nocache:
2922 return None
2923 if not self.is_derived():
2924 return None
2925 return self.get_build_env().get_CacheDir().retrieve(self)
2926
2949
2951 """Called just after this node has been marked
2952 up-to-date or was built completely.
2953
2954 This is where we try to release as many target node infos
2955 as possible for clean builds and update runs, in order
2956 to minimize the overall memory consumption.
2957
2958 We'd like to remove a lot more attributes like self.sources
2959 and self.sources_set, but they might get used
2960 in a next build step. For example, during configuration
2961 the source files for a built E{*}.o file are used to figure out
2962 which linker to use for the resulting Program (gcc vs. g++)!
2963 That's why we check for the 'keep_targetinfo' attribute,
2964 config Nodes and the Interactive mode just don't allow
2965 an early release of most variables.
2966
2967 In the same manner, we can't simply remove the self.attributes
2968 here. The smart linking relies on the shared flag, and some
2969 parts of the java Tool use it to transport information
2970 about nodes...
2971
2972 @see: built() and Node.release_target_info()
2973 """
2974 if (self.released_target_info or SCons.Node.interactive):
2975 return
2976
2977 if not hasattr(self.attributes, 'keep_targetinfo'):
2978
2979
2980 self.changed(allowcache=True)
2981 self.get_contents_sig()
2982 self.get_build_env()
2983
2984 self.executor = None
2985 self._memo.pop('rfile', None)
2986 self.prerequisites = None
2987
2988 if not len(self.ignore_set):
2989 self.ignore_set = None
2990 if not len(self.implicit_set):
2991 self.implicit_set = None
2992 if not len(self.depends_set):
2993 self.depends_set = None
2994 if not len(self.ignore):
2995 self.ignore = None
2996 if not len(self.depends):
2997 self.depends = None
2998
2999
3000 self.released_target_info = True
3001
3003 if self.rexists():
3004 return None
3005 scb = self.dir.src_builder()
3006 if scb is _null:
3007 scb = None
3008 if scb is not None:
3009 try:
3010 b = self.builder
3011 except AttributeError:
3012 b = None
3013 if b is None:
3014 self.builder_set(scb)
3015 return scb
3016
3018 """Return whether this Node has a source builder or not.
3019
3020 If this Node doesn't have an explicit source code builder, this
3021 is where we figure out, on the fly, if there's a transparent
3022 source code builder for it.
3023
3024 Note that if we found a source builder, we also set the
3025 self.builder attribute, so that all of the methods that actually
3026 *build* this file don't have to do anything different.
3027 """
3028 try:
3029 scb = self.sbuilder
3030 except AttributeError:
3031 scb = self.sbuilder = self.find_src_builder()
3032 return scb is not None
3033
3040
3048
3049
3050
3051
3052
3056
3070
3071
3072
3073
3074
3081
3096
3097 @SCons.Memoize.CountMethodCall
3106
3107
3108
3109
3110
3112 """
3113 Returns the content signature currently stored for this node
3114 if it's been unmodified longer than the max_drift value, or the
3115 max_drift value is 0. Returns None otherwise.
3116 """
3117 old = self.get_stored_info()
3118 mtime = self.get_timestamp()
3119
3120 max_drift = self.fs.max_drift
3121 if max_drift > 0:
3122 if (time.time() - mtime) > max_drift:
3123 try:
3124 n = old.ninfo
3125 if n.timestamp and n.csig and n.timestamp == mtime:
3126 return n.csig
3127 except AttributeError:
3128 pass
3129 elif max_drift == 0:
3130 try:
3131 return old.ninfo.csig
3132 except AttributeError:
3133 pass
3134
3135 return None
3136
3173
3174
3175
3176
3177
3181
3205
3206 - def changed(self, node=None, allowcache=False):
3207 """
3208 Returns if the node is up-to-date with respect to the BuildInfo
3209 stored last time it was built.
3210
3211 For File nodes this is basically a wrapper around Node.changed(),
3212 but we allow the return value to get cached after the reference
3213 to the Executor got released in release_target_info().
3214
3215 @see: Node.changed()
3216 """
3217 if node is None:
3218 try:
3219 return self._memo['changed']
3220 except KeyError:
3221 pass
3222
3223 has_changed = SCons.Node.Node.changed(self, node)
3224 if allowcache:
3225 self._memo['changed'] = has_changed
3226 return has_changed
3227
3228 - def changed_content(self, target, prev_ni):
3229 cur_csig = self.get_csig()
3230 try:
3231 return cur_csig != prev_ni.csig
3232 except AttributeError:
3233 return 1
3234
3237
3238 - def changed_timestamp_then_content(self, target, prev_ni):
3239 if not self.changed_timestamp_match(target, prev_ni):
3240 try:
3241 self.get_ninfo().csig = prev_ni.csig
3242 except AttributeError:
3243 pass
3244 return False
3245 return self.changed_content(target, prev_ni)
3246
3252
3258
3286
3287 @SCons.Memoize.CountMethodCall
3319
3321 return str(self.rfile())
3322
3324 """
3325 Fetch a Node's content signature for purposes of computing
3326 another Node's cachesig.
3327
3328 This is a wrapper around the normal get_csig() method that handles
3329 the somewhat obscure case of using CacheDir with the -n option.
3330 Any files that don't exist would normally be "built" by fetching
3331 them from the cache, but the normal get_csig() method will try
3332 to open up the local file, which doesn't exist because the -n
3333 option meant we didn't actually pull the file from cachedir.
3334 But since the file *does* actually exist in the cachedir, we
3335 can use its contents for the csig.
3336 """
3337 try:
3338 return self.cachedir_csig
3339 except AttributeError:
3340 pass
3341
3342 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
3343 if not self.exists() and cachefile and os.path.exists(cachefile):
3344 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
3345 SCons.Node.FS.File.md5_chunksize * 1024)
3346 else:
3347 self.cachedir_csig = self.get_csig()
3348 return self.cachedir_csig
3349
3350 - def get_contents_sig(self):
3351 """
3352 A helper method for get_cachedir_bsig.
3353
3354 It computes and returns the signature for this
3355 node's contents.
3356 """
3357
3358 try:
3359 return self.contentsig
3360 except AttributeError:
3361 pass
3362
3363 executor = self.get_executor()
3364
3365 result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
3366 return result
3367
3369 """
3370 Return the signature for a cached file, including
3371 its children.
3372
3373 It adds the path of the cached file to the cache signature,
3374 because multiple targets built by the same action will all
3375 have the same build signature, and we have to differentiate
3376 them somehow.
3377 """
3378 try:
3379 return self.cachesig
3380 except AttributeError:
3381 pass
3382
3383
3384 children = self.children()
3385 sigs = [n.get_cachedir_csig() for n in children]
3386
3387 sigs.append(self.get_contents_sig())
3388
3389 sigs.append(self.get_internal_path())
3390
3391 result = self.cachesig = SCons.Util.MD5collect(sigs)
3392 return result
3393
3394 default_fs = None
3401
3403 """
3404 """
3405
3408
3410 """
3411 A helper method for find_file() that looks up a directory for
3412 a file we're trying to find. This only creates the Dir Node if
3413 it exists on-disk, since if the directory doesn't exist we know
3414 we won't find any files in it... :-)
3415
3416 It would be more compact to just use this as a nested function
3417 with a default keyword argument (see the commented-out version
3418 below), but that doesn't work unless you have nested scopes,
3419 so we define it here just so this work under Python 1.5.2.
3420 """
3421 if fd is None:
3422 fd = self.default_filedir
3423 dir, name = os.path.split(fd)
3424 drive, d = _my_splitdrive(dir)
3425 if not name and d[:1] in ('/', OS_SEP):
3426
3427 return p.fs.get_root(drive)
3428 if dir:
3429 p = self.filedir_lookup(p, dir)
3430 if not p:
3431 return None
3432 norm_name = _my_normcase(name)
3433 try:
3434 node = p.entries[norm_name]
3435 except KeyError:
3436 return p.dir_on_disk(name)
3437 if isinstance(node, Dir):
3438 return node
3439 if isinstance(node, Entry):
3440 node.must_be_same(Dir)
3441 return node
3442 return None
3443
3445 return (filename, paths)
3446
3447 @SCons.Memoize.CountDictCall(_find_file_key)
3448 - def find_file(self, filename, paths, verbose=None):
3449 """
3450 Find a node corresponding to either a derived file or a file that exists already.
3451
3452 Only the first file found is returned, and none is returned if no file is found.
3453
3454 filename: A filename to find
3455 paths: A list of directory path *nodes* to search in. Can be represented as a list, a tuple, or a callable that is called with no arguments and returns the list or tuple.
3456
3457 returns The node created from the found file.
3458
3459 """
3460 memo_key = self._find_file_key(filename, paths)
3461 try:
3462 memo_dict = self._memo['find_file']
3463 except KeyError:
3464 memo_dict = {}
3465 self._memo['find_file'] = memo_dict
3466 else:
3467 try:
3468 return memo_dict[memo_key]
3469 except KeyError:
3470 pass
3471
3472 if verbose and not callable(verbose):
3473 if not SCons.Util.is_String(verbose):
3474 verbose = "find_file"
3475 _verbose = u' %s: ' % verbose
3476 verbose = lambda s: sys.stdout.write(_verbose + s)
3477
3478 filedir, filename = os.path.split(filename)
3479 if filedir:
3480 self.default_filedir = filedir
3481 paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3482
3483 result = None
3484 for dir in paths:
3485 if verbose:
3486 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3487 node, d = dir.srcdir_find_file(filename)
3488 if node:
3489 if verbose:
3490 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3491 result = node
3492 break
3493
3494 memo_dict[memo_key] = result
3495
3496 return result
3497
3498 find_file = FileFinder().find_file
3502 """
3503 Invalidate the memoized values of all Nodes (files or directories)
3504 that are associated with the given entries. Has been added to
3505 clear the cache of nodes affected by a direct execution of an
3506 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3507 inconsistent if the action is run through Execute(). The argument
3508 `targets` can be a single Node object or filename, or a sequence
3509 of Nodes/filenames.
3510 """
3511 from traceback import extract_stack
3512
3513
3514
3515
3516
3517
3518 for f in extract_stack():
3519 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3520 break
3521 else:
3522
3523 return
3524
3525 if not SCons.Util.is_List(targets):
3526 targets = [targets]
3527
3528 for entry in targets:
3529
3530
3531 try:
3532 entry.clear_memoized_values()
3533 except AttributeError:
3534
3535
3536
3537 node = get_default_fs().Entry(entry)
3538 if node:
3539 node.clear_memoized_values()
3540
3541
3542
3543
3544
3545
3546