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 5357 2011/09/09 21:31:03 bdeegan"
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 from SCons.Debug import logInstanceCreation
48 import SCons.Errors
49 import SCons.Memoize
50 import SCons.Node
51 import SCons.Node.Alias
52 import SCons.Subst
53 import SCons.Util
54 import SCons.Warnings
55
56 from SCons.Debug import Trace
57
58 do_store_info = True
59 print_duplicate = 0
60
61
62 -class EntryProxyAttributeError(AttributeError):
63 """
64 An AttributeError subclass for recording and displaying the name
65 of the underlying Entry involved in an AttributeError exception.
66 """
67 - def __init__(self, entry_proxy, attribute):
68 AttributeError.__init__(self)
69 self.entry_proxy = entry_proxy
70 self.attribute = attribute
72 entry = self.entry_proxy.get()
73 fmt = "%s instance %s has no attribute %s"
74 return fmt % (entry.__class__.__name__,
75 repr(entry.name),
76 repr(self.attribute))
77
78
79
80 default_max_drift = 2*24*60*60
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 Save_Strings = None
101
105
106
107
108
109
110
111
112
113 do_splitdrive = None
114 _my_splitdrive =None
115
134 else:
135 def splitdrive(p):
136 if p[1:2] == ':':
137 return p[:2], p[2:]
138 return '', p
139 _my_splitdrive = splitdrive
140
141
142
143 global OS_SEP
144 global UNC_PREFIX
145 global os_sep_is_slash
146
147 OS_SEP = os.sep
148 UNC_PREFIX = OS_SEP + OS_SEP
149 os_sep_is_slash = OS_SEP == '/'
150
151 initialize_do_splitdrive()
152
153
154 needs_normpath_check = re.compile(
155 r'''
156 # We need to renormalize the path if it contains any consecutive
157 # '/' characters.
158 .*// |
159
160 # We need to renormalize the path if it contains a '..' directory.
161 # Note that we check for all the following cases:
162 #
163 # a) The path is a single '..'
164 # b) The path starts with '..'. E.g. '../' or '../moredirs'
165 # but we not match '..abc/'.
166 # c) The path ends with '..'. E.g. '/..' or 'dirs/..'
167 # d) The path contains a '..' in the middle.
168 # E.g. dirs/../moredirs
169
170 (.*/)?\.\.(?:/|$) |
171
172 # We need to renormalize the path if it contains a '.'
173 # directory, but NOT if it is a single '.' '/' characters. We
174 # do not want to match a single '.' because this case is checked
175 # for explicitely since this is common enough case.
176 #
177 # Note that we check for all the following cases:
178 #
179 # a) We don't match a single '.'
180 # b) We match if the path starts with '.'. E.g. './' or
181 # './moredirs' but we not match '.abc/'.
182 # c) We match if the path ends with '.'. E.g. '/.' or
183 # 'dirs/.'
184 # d) We match if the path contains a '.' in the middle.
185 # E.g. dirs/./moredirs
186
187 \./|.*/\.(?:/|$)
188
189 ''',
190 re.VERBOSE
191 )
192 needs_normpath_match = needs_normpath_check.match
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 if hasattr(os, 'link'):
221 else:
222 _hardlink_func = None
223
224 if hasattr(os, 'symlink'):
227 else:
228 _softlink_func = None
229
234
235
236 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
237 'hard-copy', 'soft-copy', 'copy']
238
239 Link_Funcs = []
240
263
265
266
267
268
269
270 src = source[0].abspath
271 dest = target[0].abspath
272 dir, file = os.path.split(dest)
273 if dir and not target[0].fs.isdir(dir):
274 os.makedirs(dir)
275 if not Link_Funcs:
276
277 set_duplicate('hard-soft-copy')
278 fs = source[0].fs
279
280 for func in Link_Funcs:
281 try:
282 func(fs, src, dest)
283 break
284 except (IOError, OSError):
285
286
287
288
289
290
291 if func == Link_Funcs[-1]:
292
293 raise
294 return 0
295
296 Link = SCons.Action.Action(LinkFunc, None)
298 return 'Local copy of %s from %s' % (target[0], source[0])
299
300 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
301
303 t = target[0]
304 t.fs.unlink(t.abspath)
305 return 0
306
307 Unlink = SCons.Action.Action(UnlinkFunc, None)
308
310 t = target[0]
311 if not t.exists():
312 t.fs.mkdir(t.abspath)
313 return 0
314
315 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
316
317 MkdirBuilder = None
318
333
336
337 _null = _Null()
338
339 DefaultSCCSBuilder = None
340 DefaultRCSBuilder = None
341
353
365
366
367 _is_cygwin = sys.platform == "cygwin"
368 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
371 else:
374
375
376
379 self.type = type
380 self.do = do
381 self.ignore = ignore
382 self.func = do
384 return self.func(*args, **kw)
385 - def set(self, list):
386 if self.type in list:
387 self.func = self.do
388 else:
389 self.func = self.ignore
390
392 result = predicate()
393 try:
394
395
396
397
398
399
400 if node._memo['stat'] is None:
401 del node._memo['stat']
402 except (AttributeError, KeyError):
403 pass
404 if result:
405 raise TypeError(errorfmt % node.abspath)
406
409
411 try:
412 rcs_dir = node.rcs_dir
413 except AttributeError:
414 if node.entry_exists_on_disk('RCS'):
415 rcs_dir = node.Dir('RCS')
416 else:
417 rcs_dir = None
418 node.rcs_dir = rcs_dir
419 if rcs_dir:
420 return rcs_dir.entry_exists_on_disk(name+',v')
421 return None
422
425
427 try:
428 sccs_dir = node.sccs_dir
429 except AttributeError:
430 if node.entry_exists_on_disk('SCCS'):
431 sccs_dir = node.Dir('SCCS')
432 else:
433 sccs_dir = None
434 node.sccs_dir = sccs_dir
435 if sccs_dir:
436 return sccs_dir.entry_exists_on_disk('s.'+name)
437 return None
438
441
442 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
443 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
444 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
445
446 diskcheckers = [
447 diskcheck_match,
448 diskcheck_rcs,
449 diskcheck_sccs,
450 ]
451
455
458
459
460
461 -class EntryProxy(SCons.Util.Proxy):
462
463 __str__ = SCons.Util.Delegate('__str__')
464
465 - def __get_abspath(self):
466 entry = self.get()
467 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
468 entry.name + "_abspath")
469
470 - def __get_filebase(self):
471 name = self.get().name
472 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
473 name + "_filebase")
474
475 - def __get_suffix(self):
476 name = self.get().name
477 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
478 name + "_suffix")
479
480 - def __get_file(self):
481 name = self.get().name
482 return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
483
484 - def __get_base_path(self):
485 """Return the file's directory and file name, with the
486 suffix stripped."""
487 entry = self.get()
488 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
489 entry.name + "_base")
490
492 """Return the path with / as the path separator,
493 regardless of platform."""
494 if os_sep_is_slash:
495 return self
496 else:
497 entry = self.get()
498 r = entry.get_path().replace(OS_SEP, '/')
499 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
500
502 """Return the path with \ as the path separator,
503 regardless of platform."""
504 if OS_SEP == '\\':
505 return self
506 else:
507 entry = self.get()
508 r = entry.get_path().replace(OS_SEP, '\\')
509 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
510
511 - def __get_srcnode(self):
512 return EntryProxy(self.get().srcnode())
513
514 - def __get_srcdir(self):
515 """Returns the directory containing the source node linked to this
516 node via VariantDir(), or the directory of this node if not linked."""
517 return EntryProxy(self.get().srcnode().dir)
518
519 - def __get_rsrcnode(self):
520 return EntryProxy(self.get().srcnode().rfile())
521
522 - def __get_rsrcdir(self):
523 """Returns the directory containing the source node linked to this
524 node via VariantDir(), or the directory of this node if not linked."""
525 return EntryProxy(self.get().srcnode().rfile().dir)
526
527 - def __get_dir(self):
528 return EntryProxy(self.get().dir)
529
530 dictSpecialAttrs = { "base" : __get_base_path,
531 "posix" : __get_posix_path,
532 "windows" : __get_windows_path,
533 "win32" : __get_windows_path,
534 "srcpath" : __get_srcnode,
535 "srcdir" : __get_srcdir,
536 "dir" : __get_dir,
537 "abspath" : __get_abspath,
538 "filebase" : __get_filebase,
539 "suffix" : __get_suffix,
540 "file" : __get_file,
541 "rsrcpath" : __get_rsrcnode,
542 "rsrcdir" : __get_rsrcdir,
543 }
544
545 - def __getattr__(self, name):
546
547
548 try:
549 attr_function = self.dictSpecialAttrs[name]
550 except KeyError:
551 try:
552 attr = SCons.Util.Proxy.__getattr__(self, name)
553 except AttributeError, e:
554
555
556
557 raise EntryProxyAttributeError(self, name)
558 return attr
559 else:
560 return attr_function(self)
561
562 -class Base(SCons.Node.Node):
563 """A generic class for file system entries. This class is for
564 when we don't know yet whether the entry being looked up is a file
565 or a directory. Instances of this class can morph into either
566 Dir or File objects by a later, more precise lookup.
567
568 Note: this class does not define __cmp__ and __hash__ for
569 efficiency reasons. SCons does a lot of comparing of
570 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
571 as fast as possible, which means we want to use Python's built-in
572 object identity comparisons.
573 """
574
575 memoizer_counters = []
576
577 - def __init__(self, name, directory, fs):
578 """Initialize a generic Node.FS.Base object.
579
580 Call the superclass initialization, take care of setting up
581 our relative and absolute paths, identify our parent
582 directory, and indicate that this node should use
583 signatures."""
584 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
585 SCons.Node.Node.__init__(self)
586
587
588
589
590
591
592 self.name = SCons.Util.silent_intern(name)
593
594 self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1])
595 self.fs = fs
596
597 assert directory, "A directory must be provided"
598
599 self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name))
600 self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name))
601 if directory.path == '.':
602 self.path = SCons.Util.silent_intern(name)
603 else:
604 self.path = SCons.Util.silent_intern(directory.entry_path(name))
605 if directory.tpath == '.':
606 self.tpath = SCons.Util.silent_intern(name)
607 else:
608 self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name))
609 self.path_elements = directory.path_elements + [self]
610
611 self.dir = directory
612 self.cwd = None
613 self.duplicate = directory.duplicate
614
616 return '"' + self.__str__() + '"'
617
619 """
620 This node, which already existed, is being looked up as the
621 specified klass. Raise an exception if it isn't.
622 """
623 if isinstance(self, klass) or klass is Entry:
624 return
625 raise TypeError("Tried to lookup %s '%s' as a %s." %\
626 (self.__class__.__name__, self.path, klass.__name__))
627
630
633
636
644
645 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
646
648 try:
649 return self._memo['_save_str']
650 except KeyError:
651 pass
652 result = sys.intern(self._get_str())
653 self._memo['_save_str'] = result
654 return result
655
680
681 rstr = __str__
682
683 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
684
686 try: return self._memo['stat']
687 except KeyError: pass
688 try: result = self.fs.stat(self.abspath)
689 except os.error: result = None
690 self._memo['stat'] = result
691 return result
692
694 return self.stat() is not None
695
698
700 st = self.stat()
701 if st: return st[stat.ST_MTIME]
702 else: return None
703
705 st = self.stat()
706 if st: return st[stat.ST_SIZE]
707 else: return None
708
710 st = self.stat()
711 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
712
714 st = self.stat()
715 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
716
717 if hasattr(os, 'symlink'):
719 try: st = self.fs.lstat(self.abspath)
720 except os.error: return 0
721 return stat.S_ISLNK(st[stat.ST_MODE])
722 else:
725
727 if self is dir:
728 return 1
729 else:
730 return self.dir.is_under(dir)
731
734
746
748 """Return path relative to the current working directory of the
749 Node.FS.Base object that owns us."""
750 if not dir:
751 dir = self.fs.getcwd()
752 if self == dir:
753 return '.'
754 path_elems = self.path_elements
755 pathname = ''
756 try: i = path_elems.index(dir)
757 except ValueError:
758 for p in path_elems[:-1]:
759 pathname += p.dirname
760 else:
761 for p in path_elems[i+1:-1]:
762 pathname += p.dirname
763 return pathname + path_elems[-1].name
764
766 """Set the source code builder for this node."""
767 self.sbuilder = builder
768 if not self.has_builder():
769 self.builder_set(builder)
770
772 """Fetch the source code builder for this node.
773
774 If there isn't one, we cache the source code builder specified
775 for the directory (which in turn will cache the value from its
776 parent directory, and so on up to the file system root).
777 """
778 try:
779 scb = self.sbuilder
780 except AttributeError:
781 scb = self.dir.src_builder()
782 self.sbuilder = scb
783 return scb
784
786 """Get the absolute path of the file."""
787 return self.abspath
788
790
791
792
793 return self.name
794
796 try:
797 return self._proxy
798 except AttributeError:
799 ret = EntryProxy(self)
800 self._proxy = ret
801 return ret
802
804 """
805
806 Generates a target entry that corresponds to this entry (usually
807 a source file) with the specified prefix and suffix.
808
809 Note that this method can be overridden dynamically for generated
810 files that need different behavior. See Tool/swig.py for
811 an example.
812 """
813 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
814
817
818 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
819
821 """
822 Return all of the directories for a given path list, including
823 corresponding "backing" directories in any repositories.
824
825 The Node lookups are relative to this Node (typically a
826 directory), so memoizing result saves cycles from looking
827 up the same path for each target in a given directory.
828 """
829 try:
830 memo_dict = self._memo['Rfindalldirs']
831 except KeyError:
832 memo_dict = {}
833 self._memo['Rfindalldirs'] = memo_dict
834 else:
835 try:
836 return memo_dict[pathlist]
837 except KeyError:
838 pass
839
840 create_dir_relative_to_self = self.Dir
841 result = []
842 for path in pathlist:
843 if isinstance(path, SCons.Node.Node):
844 result.append(path)
845 else:
846 dir = create_dir_relative_to_self(path)
847 result.extend(dir.get_all_rdirs())
848
849 memo_dict[pathlist] = result
850
851 return result
852
853 - def RDirs(self, pathlist):
854 """Search for a list of directories in the Repository list."""
855 cwd = self.cwd or self.fs._cwd
856 return cwd.Rfindalldirs(pathlist)
857
858 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
859
861 try:
862 return self._memo['rentry']
863 except KeyError:
864 pass
865 result = self
866 if not self.exists():
867 norm_name = _my_normcase(self.name)
868 for dir in self.dir.get_all_rdirs():
869 try:
870 node = dir.entries[norm_name]
871 except KeyError:
872 if dir.entry_exists_on_disk(self.name):
873 result = dir.Entry(self.name)
874 break
875 self._memo['rentry'] = result
876 return result
877
878 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
880
882 """This is the class for generic Node.FS entries--that is, things
883 that could be a File or a Dir, but we're just not sure yet.
884 Consequently, the methods in this class really exist just to
885 transform their associated object into the right class when the
886 time comes, and then call the same-named method in the transformed
887 class."""
888
889 - def diskcheck_match(self):
891
892 - def disambiguate(self, must_exist=None):
893 """
894 """
895 if self.isdir():
896 self.__class__ = Dir
897 self._morph()
898 elif self.isfile():
899 self.__class__ = File
900 self._morph()
901 self.clear()
902 else:
903
904
905
906
907
908
909
910
911
912 srcdir = self.dir.srcnode()
913 if srcdir != self.dir and \
914 srcdir.entry_exists_on_disk(self.name) and \
915 self.srcnode().isdir():
916 self.__class__ = Dir
917 self._morph()
918 elif must_exist:
919 msg = "No such file or directory: '%s'" % self.abspath
920 raise SCons.Errors.UserError(msg)
921 else:
922 self.__class__ = File
923 self._morph()
924 self.clear()
925 return self
926
928 """We're a generic Entry, but the caller is actually looking for
929 a File at this point, so morph into one."""
930 self.__class__ = File
931 self._morph()
932 self.clear()
933 return File.rfile(self)
934
935 - def scanner_key(self):
936 return self.get_suffix()
937
938 - def get_contents(self):
939 """Fetch the contents of the entry. Returns the exact binary
940 contents of the file."""
941 try:
942 self = self.disambiguate(must_exist=1)
943 except SCons.Errors.UserError:
944
945
946
947
948
949 return ''
950 else:
951 return self.get_contents()
952
954 """Fetch the decoded text contents of a Unicode encoded Entry.
955
956 Since this should return the text contents from the file
957 system, we check to see into what sort of subclass we should
958 morph this Entry."""
959 try:
960 self = self.disambiguate(must_exist=1)
961 except SCons.Errors.UserError:
962
963
964
965
966
967 return ''
968 else:
969 return self.get_text_contents()
970
971 - def must_be_same(self, klass):
972 """Called to make sure a Node is a Dir. Since we're an
973 Entry, we can morph into one."""
974 if self.__class__ is not klass:
975 self.__class__ = klass
976 self._morph()
977 self.clear()
978
979
980
981
982
983
984
985
986
987
988
989
991 """Return if the Entry exists. Check the file system to see
992 what we should turn into first. Assume a file if there's no
993 directory."""
994 return self.disambiguate().exists()
995
996 - def rel_path(self, other):
997 d = self.disambiguate()
998 if d.__class__ is Entry:
999 raise Exception("rel_path() could not disambiguate File/Dir")
1000 return d.rel_path(other)
1001
1002 - def new_ninfo(self):
1003 return self.disambiguate().new_ninfo()
1004
1005 - def changed_since_last_build(self, target, prev_ni):
1006 return self.disambiguate().changed_since_last_build(target, prev_ni)
1007
1008 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1009 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1010
1011 - def get_subst_proxy(self):
1013
1014
1015
1016 _classEntry = Entry
1017
1018
1020
1021 if SCons.Memoize.use_memoizer:
1022 __metaclass__ = SCons.Memoize.Memoized_Metaclass
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040 - def chmod(self, path, mode):
1042 - def copy(self, src, dst):
1043 return shutil.copy(src, dst)
1044 - def copy2(self, src, dst):
1045 return shutil.copy2(src, dst)
1056 - def link(self, src, dst):
1057 return os.link(src, dst)
1067 return os.rename(old, new)
1068 - def stat(self, path):
1072 - def open(self, path):
1076
1077 if hasattr(os, 'symlink'):
1080 else:
1083
1084 if hasattr(os, 'readlink'):
1087 else:
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1102
1103 memoizer_counters = []
1104
1106 """Initialize the Node.FS subsystem.
1107
1108 The supplied path is the top of the source tree, where we
1109 expect to find the top-level build file. If no path is
1110 supplied, the current directory is the default.
1111
1112 The path argument must be a valid absolute path.
1113 """
1114 if __debug__: logInstanceCreation(self, 'Node.FS')
1115
1116 self._memo = {}
1117
1118 self.Root = {}
1119 self.SConstruct_dir = None
1120 self.max_drift = default_max_drift
1121
1122 self.Top = None
1123 if path is None:
1124 self.pathTop = os.getcwd()
1125 else:
1126 self.pathTop = path
1127 self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1128
1129 self.Top = self.Dir(self.pathTop)
1130 self.Top.path = '.'
1131 self.Top.tpath = '.'
1132 self._cwd = self.Top
1133
1134 DirNodeInfo.fs = self
1135 FileNodeInfo.fs = self
1136
1138 self.SConstruct_dir = dir
1139
1141 return self.max_drift
1142
1144 self.max_drift = max_drift
1145
1147 if hasattr(self, "_cwd"):
1148 return self._cwd
1149 else:
1150 return "<no cwd>"
1151
1152 - def chdir(self, dir, change_os_dir=0):
1153 """Change the current working directory for lookups.
1154 If change_os_dir is true, we will also change the "real" cwd
1155 to match.
1156 """
1157 curr=self._cwd
1158 try:
1159 if dir is not None:
1160 self._cwd = dir
1161 if change_os_dir:
1162 os.chdir(dir.abspath)
1163 except OSError:
1164 self._cwd = curr
1165 raise
1166
1168 """
1169 Returns the root directory for the specified drive, creating
1170 it if necessary.
1171 """
1172 drive = _my_normcase(drive)
1173 try:
1174 return self.Root[drive]
1175 except KeyError:
1176 root = RootDir(drive, self)
1177 self.Root[drive] = root
1178 if not drive:
1179 self.Root[self.defaultDrive] = root
1180 elif drive == self.defaultDrive:
1181 self.Root[''] = root
1182 return root
1183
1184 - def _lookup(self, p, directory, fsclass, create=1):
1185 """
1186 The generic entry point for Node lookup with user-supplied data.
1187
1188 This translates arbitrary input into a canonical Node.FS object
1189 of the specified fsclass. The general approach for strings is
1190 to turn it into a fully normalized absolute path and then call
1191 the root directory's lookup_abs() method for the heavy lifting.
1192
1193 If the path name begins with '#', it is unconditionally
1194 interpreted relative to the top-level directory of this FS. '#'
1195 is treated as a synonym for the top-level SConstruct directory,
1196 much like '~' is treated as a synonym for the user's home
1197 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1198 to the 'foo' subdirectory underneath the top-level SConstruct
1199 directory.
1200
1201 If the path name is relative, then the path is looked up relative
1202 to the specified directory, or the current directory (self._cwd,
1203 typically the SConscript directory) if the specified directory
1204 is None.
1205 """
1206 if isinstance(p, Base):
1207
1208
1209 p.must_be_same(fsclass)
1210 return p
1211
1212 p = str(p)
1213
1214 if not os_sep_is_slash:
1215 p = p.replace(OS_SEP, '/')
1216
1217 if p[0:1] == '#':
1218
1219
1220
1221 p = p[1:]
1222 directory = self.Top
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234 if do_splitdrive:
1235 drive, p = _my_splitdrive(p)
1236 if drive:
1237 root = self.get_root(drive)
1238 else:
1239 root = directory.root
1240 else:
1241 root = directory.root
1242
1243
1244
1245 p = p.strip('/')
1246
1247 needs_normpath = needs_normpath_match(p)
1248
1249
1250 if p in ('', '.'):
1251 p = directory.labspath
1252 else:
1253 p = directory.labspath + '/' + p
1254 else:
1255 if do_splitdrive:
1256 drive, p = _my_splitdrive(p)
1257 if drive and not p:
1258
1259
1260
1261 p = '/'
1262 else:
1263 drive = ''
1264
1265
1266
1267 if p != '/':
1268 p = p.rstrip('/')
1269
1270 needs_normpath = needs_normpath_match(p)
1271
1272 if p[0:1] == '/':
1273
1274 root = self.get_root(drive)
1275 else:
1276
1277
1278
1279
1280 if directory:
1281 if not isinstance(directory, Dir):
1282 directory = self.Dir(directory)
1283 else:
1284 directory = self._cwd
1285
1286 if p in ('', '.'):
1287 p = directory.labspath
1288 else:
1289 p = directory.labspath + '/' + p
1290
1291 if drive:
1292 root = self.get_root(drive)
1293 else:
1294 root = directory.root
1295
1296 if needs_normpath is not None:
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306 ins = p.split('/')[1:]
1307 outs = []
1308 for d in ins:
1309 if d == '..':
1310 try:
1311 outs.pop()
1312 except IndexError:
1313 pass
1314 elif d not in ('', '.'):
1315 outs.append(d)
1316 p = '/' + '/'.join(outs)
1317
1318 return root._lookup_abs(p, fsclass, create)
1319
1320 - def Entry(self, name, directory = None, create = 1):
1321 """Look up or create a generic Entry node with the specified name.
1322 If the name is a relative path (begins with ./, ../, or a file
1323 name), then it is looked up relative to the supplied directory
1324 node, or to the top level directory of the FS (supplied at
1325 construction time) if no directory is supplied.
1326 """
1327 return self._lookup(name, directory, Entry, create)
1328
1329 - def File(self, name, directory = None, create = 1):
1330 """Look up or create a File node with the specified name. If
1331 the name is a relative path (begins with ./, ../, or a file name),
1332 then it is looked up relative to the supplied directory node,
1333 or to the top level directory of the FS (supplied at construction
1334 time) if no directory is supplied.
1335
1336 This method will raise TypeError if a directory is found at the
1337 specified path.
1338 """
1339 return self._lookup(name, directory, File, create)
1340
1341 - def Dir(self, name, directory = None, create = True):
1342 """Look up or create a Dir node with the specified name. If
1343 the name is a relative path (begins with ./, ../, or a file name),
1344 then it is looked up relative to the supplied directory node,
1345 or to the top level directory of the FS (supplied at construction
1346 time) if no directory is supplied.
1347
1348 This method will raise TypeError if a normal file is found at the
1349 specified path.
1350 """
1351 return self._lookup(name, directory, Dir, create)
1352
1353 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1354 """Link the supplied variant directory to the source directory
1355 for purposes of building files."""
1356
1357 if not isinstance(src_dir, SCons.Node.Node):
1358 src_dir = self.Dir(src_dir)
1359 if not isinstance(variant_dir, SCons.Node.Node):
1360 variant_dir = self.Dir(variant_dir)
1361 if src_dir.is_under(variant_dir):
1362 raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
1363 if variant_dir.srcdir:
1364 if variant_dir.srcdir == src_dir:
1365 return
1366 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
1367 variant_dir.link(src_dir, duplicate)
1368
1375
1377 """Create targets in corresponding variant directories
1378
1379 Climb the directory tree, and look up path names
1380 relative to any linked variant directories we find.
1381
1382 Even though this loops and walks up the tree, we don't memoize
1383 the return value because this is really only used to process
1384 the command-line targets.
1385 """
1386 targets = []
1387 message = None
1388 fmt = "building associated VariantDir targets: %s"
1389 start_dir = dir
1390 while dir:
1391 for bd in dir.variant_dirs:
1392 if start_dir.is_under(bd):
1393
1394 return [orig], fmt % str(orig)
1395 p = os.path.join(bd.path, *tail)
1396 targets.append(self.Entry(p))
1397 tail = [dir.name] + tail
1398 dir = dir.up()
1399 if targets:
1400 message = fmt % ' '.join(map(str, targets))
1401 return targets, message
1402
1403 - def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1404 """
1405 Globs
1406
1407 This is mainly a shim layer
1408 """
1409 if cwd is None:
1410 cwd = self.getcwd()
1411 return cwd.glob(pathname, ondisk, source, strings)
1412
1429
1432
1433 glob_magic_check = re.compile('[*?[]')
1434
1437
1439 """A class for directories in a file system.
1440 """
1441
1442 memoizer_counters = []
1443
1444 NodeInfo = DirNodeInfo
1445 BuildInfo = DirBuildInfo
1446
1447 - def __init__(self, name, directory, fs):
1451
1453 """Turn a file system Node (either a freshly initialized directory
1454 object or a separate Entry object) into a proper directory object.
1455
1456 Set up this directory's entries and hook it into the file
1457 system tree. Specify that directories (this Node) don't use
1458 signatures for calculating whether they're current.
1459 """
1460
1461 self.repositories = []
1462 self.srcdir = None
1463
1464 self.entries = {}
1465 self.entries['.'] = self
1466 self.entries['..'] = self.dir
1467 self.cwd = self
1468 self.searched = 0
1469 self._sconsign = None
1470 self.variant_dirs = []
1471 self.root = self.dir.root
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482 self.dirname = self.name + OS_SEP
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492 if not hasattr(self, 'executor'):
1493 self.builder = get_MkdirBuilder()
1494 self.get_executor().set_action_list(self.builder.action)
1495 else:
1496
1497 l = self.get_executor().action_list
1498 a = get_MkdirBuilder().action
1499 l.insert(0, a)
1500 self.get_executor().set_action_list(l)
1501
1505
1507 """Called when we change the repository(ies) for a directory.
1508 This clears any cached information that is invalidated by changing
1509 the repository."""
1510
1511 for node in self.entries.values():
1512 if node != self.dir:
1513 if node != self and isinstance(node, Dir):
1514 node.__clearRepositoryCache(duplicate)
1515 else:
1516 node.clear()
1517 try:
1518 del node._srcreps
1519 except AttributeError:
1520 pass
1521 if duplicate is not None:
1522 node.duplicate=duplicate
1523
1525 if node != self:
1526 node.duplicate = node.get_dir().duplicate
1527
1528 - def Entry(self, name):
1529 """
1530 Looks up or creates an entry node named 'name' relative to
1531 this directory.
1532 """
1533 return self.fs.Entry(name, self)
1534
1535 - def Dir(self, name, create=True):
1536 """
1537 Looks up or creates a directory node named 'name' relative to
1538 this directory.
1539 """
1540 return self.fs.Dir(name, self, create)
1541
1542 - def File(self, name):
1543 """
1544 Looks up or creates a file node named 'name' relative to
1545 this directory.
1546 """
1547 return self.fs.File(name, self)
1548
1549 - def link(self, srcdir, duplicate):
1550 """Set this directory as the variant directory for the
1551 supplied source directory."""
1552 self.srcdir = srcdir
1553 self.duplicate = duplicate
1554 self.__clearRepositoryCache(duplicate)
1555 srcdir.variant_dirs.append(self)
1556
1558 """Returns a list of repositories for this directory.
1559 """
1560 if self.srcdir and not self.duplicate:
1561 return self.srcdir.get_all_rdirs() + self.repositories
1562 return self.repositories
1563
1564 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1565
1567 try:
1568 return list(self._memo['get_all_rdirs'])
1569 except KeyError:
1570 pass
1571
1572 result = [self]
1573 fname = '.'
1574 dir = self
1575 while dir:
1576 for rep in dir.getRepositories():
1577 result.append(rep.Dir(fname))
1578 if fname == '.':
1579 fname = dir.name
1580 else:
1581 fname = dir.name + OS_SEP + fname
1582 dir = dir.up()
1583
1584 self._memo['get_all_rdirs'] = list(result)
1585
1586 return result
1587
1589 if dir != self and not dir in self.repositories:
1590 self.repositories.append(dir)
1591 dir.tpath = '.'
1592 self.__clearRepositoryCache()
1593
1596
1599
1600 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1601
1603 """Return a path to "other" relative to this directory.
1604 """
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616 try:
1617 memo_dict = self._memo['rel_path']
1618 except KeyError:
1619 memo_dict = {}
1620 self._memo['rel_path'] = memo_dict
1621 else:
1622 try:
1623 return memo_dict[other]
1624 except KeyError:
1625 pass
1626
1627 if self is other:
1628 result = '.'
1629
1630 elif not other in self.path_elements:
1631 try:
1632 other_dir = other.get_dir()
1633 except AttributeError:
1634 result = str(other)
1635 else:
1636 if other_dir is None:
1637 result = other.name
1638 else:
1639 dir_rel_path = self.rel_path(other_dir)
1640 if dir_rel_path == '.':
1641 result = other.name
1642 else:
1643 result = dir_rel_path + OS_SEP + other.name
1644 else:
1645 i = self.path_elements.index(other) + 1
1646
1647 path_elems = ['..'] * (len(self.path_elements) - i) \
1648 + [n.name for n in other.path_elements[i:]]
1649
1650 result = OS_SEP.join(path_elems)
1651
1652 memo_dict[other] = result
1653
1654 return result
1655
1659
1663
1665 """Return this directory's implicit dependencies.
1666
1667 We don't bother caching the results because the scan typically
1668 shouldn't be requested more than once (as opposed to scanning
1669 .h file contents, which can be requested as many times as the
1670 files is #included by other files).
1671 """
1672 if not scanner:
1673 return []
1674
1675
1676
1677
1678
1679
1680
1681
1682 self.clear()
1683 return scanner(self, env, path)
1684
1685
1686
1687
1688
1691
1697
1698
1699
1700
1701
1703 """Create this directory, silently and without worrying about
1704 whether the builder is the default or not."""
1705 listDirs = []
1706 parent = self
1707 while parent:
1708 if parent.exists():
1709 break
1710 listDirs.append(parent)
1711 p = parent.up()
1712 if p is None:
1713
1714
1715 raise SCons.Errors.StopError(parent.path)
1716 parent = p
1717 listDirs.reverse()
1718 for dirnode in listDirs:
1719 try:
1720
1721
1722
1723
1724 SCons.Node.Node.build(dirnode)
1725 dirnode.get_executor().nullify()
1726
1727
1728
1729
1730 dirnode.clear()
1731 except OSError:
1732 pass
1733
1737
1739 """Return any corresponding targets in a variant directory.
1740 """
1741 return self.fs.variant_dir_target_climb(self, self, [])
1742
1744 """A directory does not get scanned."""
1745 return None
1746
1748 """We already emit things in text, so just return the binary
1749 version."""
1750 return self.get_contents()
1751
1752 - def get_contents(self):
1753 """Return content signatures and names of all our children
1754 separated by new-lines. Ensure that the nodes are sorted."""
1755 contents = []
1756 for node in sorted(self.children(), key=lambda t: t.name):
1757 contents.append('%s %s\n' % (node.get_csig(), node.name))
1758 return ''.join(contents)
1759
1761 """Compute the content signature for Directory nodes. In
1762 general, this is not needed and the content signature is not
1763 stored in the DirNodeInfo. However, if get_contents on a Dir
1764 node is called which has a child directory, the child
1765 directory should return the hash of its contents."""
1766 contents = self.get_contents()
1767 return SCons.Util.MD5signature(contents)
1768
1771
1772 changed_since_last_build = SCons.Node.Node.state_has_changed
1773
1784
1786 if not self.exists():
1787 norm_name = _my_normcase(self.name)
1788 for dir in self.dir.get_all_rdirs():
1789 try: node = dir.entries[norm_name]
1790 except KeyError: node = dir.dir_on_disk(self.name)
1791 if node and node.exists() and \
1792 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1793 return node
1794 return self
1795
1797 """Return the .sconsign file info for this directory,
1798 creating it first if necessary."""
1799 if not self._sconsign:
1800 import SCons.SConsign
1801 self._sconsign = SCons.SConsign.ForDirectory(self)
1802 return self._sconsign
1803
1805 """Dir has a special need for srcnode()...if we
1806 have a srcdir attribute set, then that *is* our srcnode."""
1807 if self.srcdir:
1808 return self.srcdir
1809 return Base.srcnode(self)
1810
1812 """Return the latest timestamp from among our children"""
1813 stamp = 0
1814 for kid in self.children():
1815 if kid.get_timestamp() > stamp:
1816 stamp = kid.get_timestamp()
1817 return stamp
1818
1819 - def entry_abspath(self, name):
1820 return self.abspath + OS_SEP + name
1821
1822 - def entry_labspath(self, name):
1823 return self.labspath + '/' + name
1824
1825 - def entry_path(self, name):
1826 return self.path + OS_SEP + name
1827
1828 - def entry_tpath(self, name):
1829 return self.tpath + OS_SEP + name
1830
1831 - def entry_exists_on_disk(self, name):
1832 try:
1833 d = self.on_disk_entries
1834 except AttributeError:
1835 d = {}
1836 try:
1837 entries = os.listdir(self.abspath)
1838 except OSError:
1839 pass
1840 else:
1841 for entry in map(_my_normcase, entries):
1842 d[entry] = True
1843 self.on_disk_entries = d
1844 if sys.platform == 'win32':
1845 name = _my_normcase(name)
1846 result = d.get(name)
1847 if result is None:
1848
1849
1850 result = os.path.exists(self.abspath + OS_SEP + name)
1851 d[name] = result
1852 return result
1853 else:
1854 return name in d
1855
1856 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1857
1859 try:
1860 return self._memo['srcdir_list']
1861 except KeyError:
1862 pass
1863
1864 result = []
1865
1866 dirname = '.'
1867 dir = self
1868 while dir:
1869 if dir.srcdir:
1870 result.append(dir.srcdir.Dir(dirname))
1871 dirname = dir.name + OS_SEP + dirname
1872 dir = dir.up()
1873
1874 self._memo['srcdir_list'] = result
1875
1876 return result
1877
1894
1897
1898 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1899
1901 try:
1902 memo_dict = self._memo['srcdir_find_file']
1903 except KeyError:
1904 memo_dict = {}
1905 self._memo['srcdir_find_file'] = memo_dict
1906 else:
1907 try:
1908 return memo_dict[filename]
1909 except KeyError:
1910 pass
1911
1912 def func(node):
1913 if (isinstance(node, File) or isinstance(node, Entry)) and \
1914 (node.is_derived() or node.exists()):
1915 return node
1916 return None
1917
1918 norm_name = _my_normcase(filename)
1919
1920 for rdir in self.get_all_rdirs():
1921 try: node = rdir.entries[norm_name]
1922 except KeyError: node = rdir.file_on_disk(filename)
1923 else: node = func(node)
1924 if node:
1925 result = (node, self)
1926 memo_dict[filename] = result
1927 return result
1928
1929 for srcdir in self.srcdir_list():
1930 for rdir in srcdir.get_all_rdirs():
1931 try: node = rdir.entries[norm_name]
1932 except KeyError: node = rdir.file_on_disk(filename)
1933 else: node = func(node)
1934 if node:
1935 result = (File(filename, self, self.fs), srcdir)
1936 memo_dict[filename] = result
1937 return result
1938
1939 result = (None, None)
1940 memo_dict[filename] = result
1941 return result
1942
1951
1962
1963 - def walk(self, func, arg):
1964 """
1965 Walk this directory tree by calling the specified function
1966 for each directory in the tree.
1967
1968 This behaves like the os.path.walk() function, but for in-memory
1969 Node.FS.Dir objects. The function takes the same arguments as
1970 the functions passed to os.path.walk():
1971
1972 func(arg, dirname, fnames)
1973
1974 Except that "dirname" will actually be the directory *Node*,
1975 not the string. The '.' and '..' entries are excluded from
1976 fnames. The fnames list may be modified in-place to filter the
1977 subdirectories visited or otherwise impose a specific order.
1978 The "arg" argument is always passed to func() and may be used
1979 in any way (or ignored, passing None is common).
1980 """
1981 entries = self.entries
1982 names = list(entries.keys())
1983 names.remove('.')
1984 names.remove('..')
1985 func(arg, self, names)
1986 for dirname in [n for n in names if isinstance(entries[n], Dir)]:
1987 entries[dirname].walk(func, arg)
1988
1989 - def glob(self, pathname, ondisk=True, source=False, strings=False):
1990 """
1991 Returns a list of Nodes (or strings) matching a specified
1992 pathname pattern.
1993
1994 Pathname patterns follow UNIX shell semantics: * matches
1995 any-length strings of any characters, ? matches any character,
1996 and [] can enclose lists or ranges of characters. Matches do
1997 not span directory separators.
1998
1999 The matches take into account Repositories, returning local
2000 Nodes if a corresponding entry exists in a Repository (either
2001 an in-memory Node or something on disk).
2002
2003 By defafult, the glob() function matches entries that exist
2004 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
2005 argument to False (or some other non-true value) causes the glob()
2006 function to only match in-memory Nodes. The default behavior is
2007 to return both the on-disk and in-memory Nodes.
2008
2009 The "source" argument, when true, specifies that corresponding
2010 source Nodes must be returned if you're globbing in a build
2011 directory (initialized with VariantDir()). The default behavior
2012 is to return Nodes local to the VariantDir().
2013
2014 The "strings" argument, when true, returns the matches as strings,
2015 not Nodes. The strings are path names relative to this directory.
2016
2017 The underlying algorithm is adapted from the glob.glob() function
2018 in the Python library (but heavily modified), and uses fnmatch()
2019 under the covers.
2020 """
2021 dirname, basename = os.path.split(pathname)
2022 if not dirname:
2023 return sorted(self._glob1(basename, ondisk, source, strings),
2024 key=lambda t: str(t))
2025 if has_glob_magic(dirname):
2026 list = self.glob(dirname, ondisk, source, strings=False)
2027 else:
2028 list = [self.Dir(dirname, create=True)]
2029 result = []
2030 for dir in list:
2031 r = dir._glob1(basename, ondisk, source, strings)
2032 if strings:
2033 r = [os.path.join(str(dir), x) for x in r]
2034 result.extend(r)
2035 return sorted(result, key=lambda a: str(a))
2036
2037 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2038 """
2039 Globs for and returns a list of entry names matching a single
2040 pattern in this directory.
2041
2042 This searches any repositories and source directories for
2043 corresponding entries and returns a Node (or string) relative
2044 to the current directory if an entry is found anywhere.
2045
2046 TODO: handle pattern with no wildcard
2047 """
2048 search_dir_list = self.get_all_rdirs()
2049 for srcdir in self.srcdir_list():
2050 search_dir_list.extend(srcdir.get_all_rdirs())
2051
2052 selfEntry = self.Entry
2053 names = []
2054 for dir in search_dir_list:
2055
2056
2057
2058 node_names = [ v.name for k, v in dir.entries.items()
2059 if k not in ('.', '..') ]
2060 names.extend(node_names)
2061 if not strings:
2062
2063
2064 for name in node_names: selfEntry(name)
2065 if ondisk:
2066 try:
2067 disk_names = os.listdir(dir.abspath)
2068 except os.error:
2069 continue
2070 names.extend(disk_names)
2071 if not strings:
2072
2073
2074
2075
2076
2077
2078
2079
2080 if pattern[0] != '.':
2081
2082 disk_names = [x for x in disk_names if x[0] != '.']
2083 disk_names = fnmatch.filter(disk_names, pattern)
2084 dirEntry = dir.Entry
2085 for name in disk_names:
2086
2087
2088 name = './' + name
2089 node = dirEntry(name).disambiguate()
2090 n = selfEntry(name)
2091 if n.__class__ != node.__class__:
2092 n.__class__ = node.__class__
2093 n._morph()
2094
2095 names = set(names)
2096 if pattern[0] != '.':
2097
2098 names = [x for x in names if x[0] != '.']
2099 names = fnmatch.filter(names, pattern)
2100
2101 if strings:
2102 return names
2103
2104
2105 return [self.entries[_my_normcase(n)] for n in names]
2106
2108 """A class for the root directory of a file system.
2109
2110 This is the same as a Dir class, except that the path separator
2111 ('/' or '\\') is actually part of the name, so we don't need to
2112 add a separator when creating the path names of entries within
2113 this directory.
2114 """
2116 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
2117
2118
2119
2120 self.abspath = ''
2121 self.labspath = ''
2122 self.path = ''
2123 self.tpath = ''
2124 self.path_elements = []
2125 self.duplicate = 0
2126 self.root = self
2127
2128
2129 if drive == '':
2130
2131 name = OS_SEP
2132 dirname = OS_SEP
2133 elif drive == '//':
2134
2135 name = UNC_PREFIX
2136 dirname = UNC_PREFIX
2137 else:
2138
2139 name = drive
2140 dirname = drive + OS_SEP
2141
2142 Base.__init__(self, name, self, fs)
2143
2144
2145
2146
2147
2148
2149 self.abspath = dirname
2150 self.labspath = ''
2151 self.path = dirname
2152 self.tpath = dirname
2153 self._morph()
2154
2155
2156 self.dirname = dirname
2157
2158 self._lookupDict = {}
2159
2160 self._lookupDict[''] = self
2161 self._lookupDict['/'] = self
2162
2163
2164
2165
2166 if not has_unc:
2167 self._lookupDict['//'] = self
2168
2173
2175 """
2176 Fast (?) lookup of a *normalized* absolute path.
2177
2178 This method is intended for use by internal lookups with
2179 already-normalized path data. For general-purpose lookups,
2180 use the FS.Entry(), FS.Dir() or FS.File() methods.
2181
2182 The caller is responsible for making sure we're passed a
2183 normalized absolute path; we merely let Python's dictionary look
2184 up and return the One True Node.FS object for the path.
2185
2186 If a Node for the specified "p" doesn't already exist, and
2187 "create" is specified, the Node may be created after recursive
2188 invocation to find or create the parent directory or directories.
2189 """
2190 k = _my_normcase(p)
2191 try:
2192 result = self._lookupDict[k]
2193 except KeyError:
2194 if not create:
2195 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2196 raise SCons.Errors.UserError(msg)
2197
2198
2199
2200
2201
2202 last_slash = p.rindex('/')
2203 if (last_slash >= 0):
2204 dir_name = p[:last_slash]
2205 file_name = p[last_slash+1:]
2206 else:
2207 dir_name = p
2208 file_name = ''
2209
2210 dir_node = self._lookup_abs(dir_name, Dir)
2211 result = klass(file_name, dir_node, self.fs)
2212
2213
2214
2215 result.diskcheck_match()
2216
2217 self._lookupDict[k] = result
2218 dir_node.entries[_my_normcase(file_name)] = result
2219 dir_node.implicit = None
2220 else:
2221
2222
2223 result.must_be_same(klass)
2224 return result
2225
2228
2229 - def entry_abspath(self, name):
2230 return self.abspath + name
2231
2232 - def entry_labspath(self, name):
2234
2235 - def entry_path(self, name):
2236 return self.path + name
2237
2238 - def entry_tpath(self, name):
2239 return self.tpath + name
2240
2242 if self is dir:
2243 return 1
2244 else:
2245 return 0
2246
2249
2252
2255
2274
2276 current_version_id = 1
2277
2279 """
2280 Converts this FileBuildInfo object for writing to a .sconsign file
2281
2282 This replaces each Node in our various dependency lists with its
2283 usual string representation: relative to the top-level SConstruct
2284 directory, or an absolute path if it's outside.
2285 """
2286 if os_sep_is_slash:
2287 node_to_str = str
2288 else:
2289 def node_to_str(n):
2290 try:
2291 s = n.path
2292 except AttributeError:
2293 s = str(n)
2294 else:
2295 s = s.replace(OS_SEP, '/')
2296 return s
2297 for attr in ['bsources', 'bdepends', 'bimplicit']:
2298 try:
2299 val = getattr(self, attr)
2300 except AttributeError:
2301 pass
2302 else:
2303 setattr(self, attr, list(map(node_to_str, val)))
2305 """
2306 Converts a newly-read FileBuildInfo object for in-SCons use
2307
2308 For normal up-to-date checking, we don't have any conversion to
2309 perform--but we're leaving this method here to make that clear.
2310 """
2311 pass
2313 """
2314 Prepares a FileBuildInfo object for explaining what changed
2315
2316 The bsources, bdepends and bimplicit lists have all been
2317 stored on disk as paths relative to the top-level SConstruct
2318 directory. Convert the strings to actual Nodes (for use by the
2319 --debug=explain code and --implicit-cache).
2320 """
2321 attrs = [
2322 ('bsources', 'bsourcesigs'),
2323 ('bdepends', 'bdependsigs'),
2324 ('bimplicit', 'bimplicitsigs'),
2325 ]
2326 for (nattr, sattr) in attrs:
2327 try:
2328 strings = getattr(self, nattr)
2329 nodeinfos = getattr(self, sattr)
2330 except AttributeError:
2331 continue
2332 nodes = []
2333 for s, ni in zip(strings, nodeinfos):
2334 if not isinstance(s, SCons.Node.Node):
2335 s = ni.str_to_node(s)
2336 nodes.append(s)
2337 setattr(self, nattr, nodes)
2347
2349 """A class for files in a file system.
2350 """
2351
2352 memoizer_counters = []
2353
2354 NodeInfo = FileNodeInfo
2355 BuildInfo = FileBuildInfo
2356
2357 md5_chunksize = 64
2358
2362
2363 - def __init__(self, name, directory, fs):
2367
2368 - def Entry(self, name):
2369 """Create an entry node named 'name' relative to
2370 the directory of this file."""
2371 return self.dir.Entry(name)
2372
2373 - def Dir(self, name, create=True):
2374 """Create a directory node named 'name' relative to
2375 the directory of this file."""
2376 return self.dir.Dir(name, create=create)
2377
2378 - def Dirs(self, pathlist):
2379 """Create a list of directories relative to the SConscript
2380 directory of this file."""
2381 return [self.Dir(p) for p in pathlist]
2382
2383 - def File(self, name):
2384 """Create a file node named 'name' relative to
2385 the directory of this file."""
2386 return self.dir.File(name)
2387
2388
2389
2390
2391
2392
2393
2394
2396 """Turn a file system node into a File object."""
2397 self.scanner_paths = {}
2398 if not hasattr(self, '_local'):
2399 self._local = 0
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411 if self.has_builder():
2412 self.changed_since_last_build = self.decide_target
2413
2416
2417 - def get_contents(self):
2418 if not self.rexists():
2419 return ''
2420 fname = self.rfile().abspath
2421 try:
2422 contents = open(fname, "rb").read()
2423 except EnvironmentError, e:
2424 if not e.filename:
2425 e.filename = fname
2426 raise
2427 return contents
2428
2429
2430
2431
2433 contents = self.get_contents()
2434
2435
2436
2437
2438
2439
2440 if contents.startswith(codecs.BOM_UTF8):
2441 return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2442 if contents.startswith(codecs.BOM_UTF16_LE):
2443 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2444 if contents.startswith(codecs.BOM_UTF16_BE):
2445 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2446 return contents
2447
2448 - def get_content_hash(self):
2449 """
2450 Compute and return the MD5 hash for this file.
2451 """
2452 if not self.rexists():
2453 return SCons.Util.MD5signature('')
2454 fname = self.rfile().abspath
2455 try:
2456 cs = SCons.Util.MD5filesignature(fname,
2457 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2458 except EnvironmentError, e:
2459 if not e.filename:
2460 e.filename = fname
2461 raise
2462 return cs
2463
2464
2465 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2466
2468 try:
2469 return self._memo['get_size']
2470 except KeyError:
2471 pass
2472
2473 if self.rexists():
2474 size = self.rfile().getsize()
2475 else:
2476 size = 0
2477
2478 self._memo['get_size'] = size
2479
2480 return size
2481
2482 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2483
2485 try:
2486 return self._memo['get_timestamp']
2487 except KeyError:
2488 pass
2489
2490 if self.rexists():
2491 timestamp = self.rfile().getmtime()
2492 else:
2493 timestamp = 0
2494
2495 self._memo['get_timestamp'] = timestamp
2496
2497 return timestamp
2498
2506
2507 convert_copy_attrs = [
2508 'bsources',
2509 'bimplicit',
2510 'bdepends',
2511 'bact',
2512 'bactsig',
2513 'ninfo',
2514 ]
2515
2516
2517 convert_sig_attrs = [
2518 'bsourcesigs',
2519 'bimplicitsigs',
2520 'bdependsigs',
2521 ]
2522
2523 - def convert_old_entry(self, old_entry):
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591 import SCons.SConsign
2592 new_entry = SCons.SConsign.SConsignEntry()
2593 new_entry.binfo = self.new_binfo()
2594 binfo = new_entry.binfo
2595 for attr in self.convert_copy_attrs:
2596 try:
2597 value = getattr(old_entry, attr)
2598 except AttributeError:
2599 continue
2600 setattr(binfo, attr, value)
2601 delattr(old_entry, attr)
2602 for attr in self.convert_sig_attrs:
2603 try:
2604 sig_list = getattr(old_entry, attr)
2605 except AttributeError:
2606 continue
2607 value = []
2608 for sig in sig_list:
2609 ninfo = self.new_ninfo()
2610 if len(sig) == 32:
2611 ninfo.csig = sig
2612 else:
2613 ninfo.timestamp = sig
2614 value.append(ninfo)
2615 setattr(binfo, attr, value)
2616 delattr(old_entry, attr)
2617 return new_entry
2618
2619 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2620
2622 try:
2623 return self._memo['get_stored_info']
2624 except KeyError:
2625 pass
2626
2627 try:
2628 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2629 except (KeyError, EnvironmentError):
2630 import SCons.SConsign
2631 sconsign_entry = SCons.SConsign.SConsignEntry()
2632 sconsign_entry.binfo = self.new_binfo()
2633 sconsign_entry.ninfo = self.new_ninfo()
2634 else:
2635 if isinstance(sconsign_entry, FileBuildInfo):
2636
2637
2638 sconsign_entry = self.convert_old_entry(sconsign_entry)
2639 try:
2640 delattr(sconsign_entry.ninfo, 'bsig')
2641 except AttributeError:
2642 pass
2643
2644 self._memo['get_stored_info'] = sconsign_entry
2645
2646 return sconsign_entry
2647
2653
2656
2658 return (id(env), id(scanner), path)
2659
2660 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2661
2663 """Return the included implicit dependencies in this file.
2664 Cache results so we only scan the file once per path
2665 regardless of how many times this information is requested.
2666 """
2667 memo_key = (id(env), id(scanner), path)
2668 try:
2669 memo_dict = self._memo['get_found_includes']
2670 except KeyError:
2671 memo_dict = {}
2672 self._memo['get_found_includes'] = memo_dict
2673 else:
2674 try:
2675 return memo_dict[memo_key]
2676 except KeyError:
2677 pass
2678
2679 if scanner:
2680
2681 result = scanner(self, env, path)
2682 result = [N.disambiguate() for N in result]
2683 else:
2684 result = []
2685
2686 memo_dict[memo_key] = result
2687
2688 return result
2689
2694
2710
2712 """Try to retrieve the node's content from a cache
2713
2714 This method is called from multiple threads in a parallel build,
2715 so only do thread safe stuff here. Do thread unsafe stuff in
2716 built().
2717
2718 Returns true iff the node was successfully retrieved.
2719 """
2720 if self.nocache:
2721 return None
2722 if not self.is_derived():
2723 return None
2724 return self.get_build_env().get_CacheDir().retrieve(self)
2725
2748
2768
2770 """Return whether this Node has a source builder or not.
2771
2772 If this Node doesn't have an explicit source code builder, this
2773 is where we figure out, on the fly, if there's a transparent
2774 source code builder for it.
2775
2776 Note that if we found a source builder, we also set the
2777 self.builder attribute, so that all of the methods that actually
2778 *build* this file don't have to do anything different.
2779 """
2780 try:
2781 scb = self.sbuilder
2782 except AttributeError:
2783 scb = self.sbuilder = self.find_src_builder()
2784 return scb is not None
2785
2787 """Return any corresponding targets in a variant directory.
2788 """
2789 if self.is_derived():
2790 return [], None
2791 return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2792
2800
2801
2802
2803
2804
2808
2823
2824
2825
2826
2827
2829 """Remove this file."""
2830 if self.exists() or self.islink():
2831 self.fs.unlink(self.path)
2832 return 1
2833 return None
2834
2850
2851 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2852
2854 try:
2855 return self._memo['exists']
2856 except KeyError:
2857 pass
2858
2859 if self.duplicate and not self.is_derived() and not self.linked:
2860 src = self.srcnode()
2861 if src is not self:
2862
2863 src = src.rfile()
2864 if src.abspath != self.abspath:
2865 if src.exists():
2866 self.do_duplicate(src)
2867
2868
2869 else:
2870
2871
2872 if print_duplicate:
2873 print "dup: no src for %s, unlinking old variant copy"%self
2874 if Base.exists(self) or self.islink():
2875 self.fs.unlink(self.path)
2876
2877
2878 self._memo['exists'] = None
2879 return None
2880 result = Base.exists(self)
2881 self._memo['exists'] = result
2882 return result
2883
2884
2885
2886
2887
2889 """
2890 Returns the content signature currently stored for this node
2891 if it's been unmodified longer than the max_drift value, or the
2892 max_drift value is 0. Returns None otherwise.
2893 """
2894 old = self.get_stored_info()
2895 mtime = self.get_timestamp()
2896
2897 max_drift = self.fs.max_drift
2898 if max_drift > 0:
2899 if (time.time() - mtime) > max_drift:
2900 try:
2901 n = old.ninfo
2902 if n.timestamp and n.csig and n.timestamp == mtime:
2903 return n.csig
2904 except AttributeError:
2905 pass
2906 elif max_drift == 0:
2907 try:
2908 return old.ninfo.csig
2909 except AttributeError:
2910 pass
2911
2912 return None
2913
2915 """
2916 Generate a node's content signature, the digested signature
2917 of its content.
2918
2919 node - the node
2920 cache - alternate node to use for the signature cache
2921 returns - the content signature
2922 """
2923 ninfo = self.get_ninfo()
2924 try:
2925 return ninfo.csig
2926 except AttributeError:
2927 pass
2928
2929 csig = self.get_max_drift_csig()
2930 if csig is None:
2931
2932 try:
2933 if self.get_size() < SCons.Node.FS.File.md5_chunksize:
2934 contents = self.get_contents()
2935 else:
2936 csig = self.get_content_hash()
2937 except IOError:
2938
2939
2940
2941
2942 csig = ''
2943 else:
2944 if not csig:
2945 csig = SCons.Util.MD5signature(contents)
2946
2947 ninfo.csig = csig
2948
2949 return csig
2950
2951
2952
2953
2954
2958
2959 - def changed_content(self, target, prev_ni):
2960 cur_csig = self.get_csig()
2961 try:
2962 return cur_csig != prev_ni.csig
2963 except AttributeError:
2964 return 1
2965
2968
2969 - def changed_timestamp_then_content(self, target, prev_ni):
2970 if not self.changed_timestamp_match(target, prev_ni):
2971 try:
2972 self.get_ninfo().csig = prev_ni.csig
2973 except AttributeError:
2974 pass
2975 return False
2976 return self.changed_content(target, prev_ni)
2977
2983
2985 try:
2986 return self.get_timestamp() != prev_ni.timestamp
2987 except AttributeError:
2988 return 1
2989
2992
2995
2996
2997
2998 changed_since_last_build = decide_source
2999
3001 T = 0
3002 if T: Trace('is_up_to_date(%s):' % self)
3003 if not self.exists():
3004 if T: Trace(' not self.exists():')
3005
3006 r = self.rfile()
3007 if r != self:
3008
3009 if not self.changed(r):
3010 if T: Trace(' changed(%s):' % r)
3011
3012 if self._local:
3013
3014 e = LocalCopy(self, r, None)
3015 if isinstance(e, SCons.Errors.BuildError):
3016 raise
3017 self.store_info()
3018 if T: Trace(' 1\n')
3019 return 1
3020 self.changed()
3021 if T: Trace(' None\n')
3022 return None
3023 else:
3024 r = self.changed()
3025 if T: Trace(' self.exists(): %s\n' % r)
3026 return not r
3027
3028 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
3029
3031 try:
3032 return self._memo['rfile']
3033 except KeyError:
3034 pass
3035 result = self
3036 if not self.exists():
3037 norm_name = _my_normcase(self.name)
3038 for dir in self.dir.get_all_rdirs():
3039 try: node = dir.entries[norm_name]
3040 except KeyError: node = dir.file_on_disk(self.name)
3041 if node and node.exists() and \
3042 (isinstance(node, File) or isinstance(node, Entry) \
3043 or not node.is_derived()):
3044 result = node
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057 result.attributes = self.attributes
3058 break
3059 self._memo['rfile'] = result
3060 return result
3061
3063 return str(self.rfile())
3064
3066 """
3067 Fetch a Node's content signature for purposes of computing
3068 another Node's cachesig.
3069
3070 This is a wrapper around the normal get_csig() method that handles
3071 the somewhat obscure case of using CacheDir with the -n option.
3072 Any files that don't exist would normally be "built" by fetching
3073 them from the cache, but the normal get_csig() method will try
3074 to open up the local file, which doesn't exist because the -n
3075 option meant we didn't actually pull the file from cachedir.
3076 But since the file *does* actually exist in the cachedir, we
3077 can use its contents for the csig.
3078 """
3079 try:
3080 return self.cachedir_csig
3081 except AttributeError:
3082 pass
3083
3084 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
3085 if not self.exists() and cachefile and os.path.exists(cachefile):
3086 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
3087 SCons.Node.FS.File.md5_chunksize * 1024)
3088 else:
3089 self.cachedir_csig = self.get_csig()
3090 return self.cachedir_csig
3091
3109
3110
3111 default_fs = None
3112
3118
3120 """
3121 """
3122 if SCons.Memoize.use_memoizer:
3123 __metaclass__ = SCons.Memoize.Memoized_Metaclass
3124
3125 memoizer_counters = []
3126
3129
3131 """
3132 A helper method for find_file() that looks up a directory for
3133 a file we're trying to find. This only creates the Dir Node if
3134 it exists on-disk, since if the directory doesn't exist we know
3135 we won't find any files in it... :-)
3136
3137 It would be more compact to just use this as a nested function
3138 with a default keyword argument (see the commented-out version
3139 below), but that doesn't work unless you have nested scopes,
3140 so we define it here just so this work under Python 1.5.2.
3141 """
3142 if fd is None:
3143 fd = self.default_filedir
3144 dir, name = os.path.split(fd)
3145 drive, d = _my_splitdrive(dir)
3146 if not name and d[:1] in ('/', OS_SEP):
3147
3148 return p.fs.get_root(drive)
3149 if dir:
3150 p = self.filedir_lookup(p, dir)
3151 if not p:
3152 return None
3153 norm_name = _my_normcase(name)
3154 try:
3155 node = p.entries[norm_name]
3156 except KeyError:
3157 return p.dir_on_disk(name)
3158 if isinstance(node, Dir):
3159 return node
3160 if isinstance(node, Entry):
3161 node.must_be_same(Dir)
3162 return node
3163 return None
3164
3166 return (filename, paths)
3167
3168 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
3169
3170 - def find_file(self, filename, paths, verbose=None):
3171 """
3172 find_file(str, [Dir()]) -> [nodes]
3173
3174 filename - a filename to find
3175 paths - a list of directory path *nodes* to search in. Can be
3176 represented as a list, a tuple, or a callable that is
3177 called with no arguments and returns the list or tuple.
3178
3179 returns - the node created from the found file.
3180
3181 Find a node corresponding to either a derived file or a file
3182 that exists already.
3183
3184 Only the first file found is returned, and none is returned
3185 if no file is found.
3186 """
3187 memo_key = self._find_file_key(filename, paths)
3188 try:
3189 memo_dict = self._memo['find_file']
3190 except KeyError:
3191 memo_dict = {}
3192 self._memo['find_file'] = memo_dict
3193 else:
3194 try:
3195 return memo_dict[memo_key]
3196 except KeyError:
3197 pass
3198
3199 if verbose and not callable(verbose):
3200 if not SCons.Util.is_String(verbose):
3201 verbose = "find_file"
3202 _verbose = u' %s: ' % verbose
3203 verbose = lambda s: sys.stdout.write(_verbose + s)
3204
3205 filedir, filename = os.path.split(filename)
3206 if filedir:
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237 self.default_filedir = filedir
3238 paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3239
3240 result = None
3241 for dir in paths:
3242 if verbose:
3243 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3244 node, d = dir.srcdir_find_file(filename)
3245 if node:
3246 if verbose:
3247 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3248 result = node
3249 break
3250
3251 memo_dict[memo_key] = result
3252
3253 return result
3254
3255 find_file = FileFinder().find_file
3256
3257
3259 """
3260 Invalidate the memoized values of all Nodes (files or directories)
3261 that are associated with the given entries. Has been added to
3262 clear the cache of nodes affected by a direct execution of an
3263 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3264 inconsistent if the action is run through Execute(). The argument
3265 `targets` can be a single Node object or filename, or a sequence
3266 of Nodes/filenames.
3267 """
3268 from traceback import extract_stack
3269
3270
3271
3272
3273
3274
3275 for f in extract_stack():
3276 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3277 break
3278 else:
3279
3280 return
3281
3282 if not SCons.Util.is_List(targets):
3283 targets = [targets]
3284
3285 for entry in targets:
3286
3287
3288 try:
3289 entry.clear_memoized_values()
3290 except AttributeError:
3291
3292
3293
3294 node = get_default_fs().Entry(entry)
3295 if node:
3296 node.clear_memoized_values()
3297
3298
3299
3300
3301
3302
3303