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 5023 2010/06/14 22:05:46 scons"
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
60
61 -class EntryProxyAttributeError(AttributeError):
62 """
63 An AttributeError subclass for recording and displaying the name
64 of the underlying Entry involved in an AttributeError exception.
65 """
66 - def __init__(self, entry_proxy, attribute):
67 AttributeError.__init__(self)
68 self.entry_proxy = entry_proxy
69 self.attribute = attribute
71 entry = self.entry_proxy.get()
72 fmt = "%s instance %s has no attribute %s"
73 return fmt % (entry.__class__.__name__,
74 repr(entry.name),
75 repr(self.attribute))
76
77
78
79 default_max_drift = 2*24*60*60
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 Save_Strings = None
100
104
105
106
107
108
109
110
111
112 do_splitdrive = None
113
118
119 initialize_do_splitdrive()
120
121
122
123 needs_normpath_check = None
124
126 """
127 Initialize the normpath_check regular expression.
128
129 This function is used by the unit tests to re-initialize the pattern
130 when testing for behavior with different values of os.sep.
131 """
132 global needs_normpath_check
133 if os.sep == '/':
134 pattern = r'.*/|\.$|\.\.$'
135 else:
136 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
137 needs_normpath_check = re.compile(pattern)
138
139 initialize_normpath_check()
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155 if hasattr(os, 'link'):
168 else:
169 _hardlink_func = None
170
171 if hasattr(os, 'symlink'):
174 else:
175 _softlink_func = None
176
181
182
183 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
184 'hard-copy', 'soft-copy', 'copy']
185
186 Link_Funcs = []
187
210
212
213
214
215
216
217 src = source[0].abspath
218 dest = target[0].abspath
219 dir, file = os.path.split(dest)
220 if dir and not target[0].fs.isdir(dir):
221 os.makedirs(dir)
222 if not Link_Funcs:
223
224 set_duplicate('hard-soft-copy')
225 fs = source[0].fs
226
227 for func in Link_Funcs:
228 try:
229 func(fs, src, dest)
230 break
231 except (IOError, OSError):
232
233
234
235
236
237
238 if func == Link_Funcs[-1]:
239
240 raise
241 return 0
242
243 Link = SCons.Action.Action(LinkFunc, None)
245 return 'Local copy of %s from %s' % (target[0], source[0])
246
247 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
248
250 t = target[0]
251 t.fs.unlink(t.abspath)
252 return 0
253
254 Unlink = SCons.Action.Action(UnlinkFunc, None)
255
257 t = target[0]
258 if not t.exists():
259 t.fs.mkdir(t.abspath)
260 return 0
261
262 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
263
264 MkdirBuilder = None
265
280
283
284 _null = _Null()
285
286 DefaultSCCSBuilder = None
287 DefaultRCSBuilder = None
288
300
312
313
314 _is_cygwin = sys.platform == "cygwin"
315 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
318 else:
321
322
323
326 self.type = type
327 self.do = do
328 self.ignore = ignore
329 self.func = do
331 return self.func(*args, **kw)
332 - def set(self, list):
333 if self.type in list:
334 self.func = self.do
335 else:
336 self.func = self.ignore
337
339 result = predicate()
340 try:
341
342
343
344
345
346
347 if node._memo['stat'] is None:
348 del node._memo['stat']
349 except (AttributeError, KeyError):
350 pass
351 if result:
352 raise TypeError(errorfmt % node.abspath)
353
356
358 try:
359 rcs_dir = node.rcs_dir
360 except AttributeError:
361 if node.entry_exists_on_disk('RCS'):
362 rcs_dir = node.Dir('RCS')
363 else:
364 rcs_dir = None
365 node.rcs_dir = rcs_dir
366 if rcs_dir:
367 return rcs_dir.entry_exists_on_disk(name+',v')
368 return None
369
372
374 try:
375 sccs_dir = node.sccs_dir
376 except AttributeError:
377 if node.entry_exists_on_disk('SCCS'):
378 sccs_dir = node.Dir('SCCS')
379 else:
380 sccs_dir = None
381 node.sccs_dir = sccs_dir
382 if sccs_dir:
383 return sccs_dir.entry_exists_on_disk('s.'+name)
384 return None
385
388
389 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
390 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
391 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
392
393 diskcheckers = [
394 diskcheck_match,
395 diskcheck_rcs,
396 diskcheck_sccs,
397 ]
398
402
405
406
407
408 -class EntryProxy(SCons.Util.Proxy):
409
410 __str__ = SCons.Util.Delegate('__str__')
411
412 - def __get_abspath(self):
413 entry = self.get()
414 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
415 entry.name + "_abspath")
416
417 - def __get_filebase(self):
421
422 - def __get_suffix(self):
426
427 - def __get_file(self):
430
431 - def __get_base_path(self):
432 """Return the file's directory and file name, with the
433 suffix stripped."""
434 entry = self.get()
435 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
436 entry.name + "_base")
437
439 """Return the path with / as the path separator,
440 regardless of platform."""
441 if os.sep == '/':
442 return self
443 else:
444 entry = self.get()
445 r = entry.get_path().replace(os.sep, '/')
446 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
447
449 """Return the path with \ as the path separator,
450 regardless of platform."""
451 if os.sep == '\\':
452 return self
453 else:
454 entry = self.get()
455 r = entry.get_path().replace(os.sep, '\\')
456 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
457
458 - def __get_srcnode(self):
459 return EntryProxy(self.get().srcnode())
460
461 - def __get_srcdir(self):
462 """Returns the directory containing the source node linked to this
463 node via VariantDir(), or the directory of this node if not linked."""
464 return EntryProxy(self.get().srcnode().dir)
465
466 - def __get_rsrcnode(self):
467 return EntryProxy(self.get().srcnode().rfile())
468
469 - def __get_rsrcdir(self):
470 """Returns the directory containing the source node linked to this
471 node via VariantDir(), or the directory of this node if not linked."""
472 return EntryProxy(self.get().srcnode().rfile().dir)
473
474 - def __get_dir(self):
475 return EntryProxy(self.get().dir)
476
477 dictSpecialAttrs = { "base" : __get_base_path,
478 "posix" : __get_posix_path,
479 "windows" : __get_windows_path,
480 "win32" : __get_windows_path,
481 "srcpath" : __get_srcnode,
482 "srcdir" : __get_srcdir,
483 "dir" : __get_dir,
484 "abspath" : __get_abspath,
485 "filebase" : __get_filebase,
486 "suffix" : __get_suffix,
487 "file" : __get_file,
488 "rsrcpath" : __get_rsrcnode,
489 "rsrcdir" : __get_rsrcdir,
490 }
491
492 - def __getattr__(self, name):
493
494
495 try:
496 attr_function = self.dictSpecialAttrs[name]
497 except KeyError:
498 try:
499 attr = SCons.Util.Proxy.__getattr__(self, name)
500 except AttributeError, e:
501
502
503
504 raise EntryProxyAttributeError(self, name)
505 return attr
506 else:
507 return attr_function(self)
508
509 -class Base(SCons.Node.Node):
510 """A generic class for file system entries. This class is for
511 when we don't know yet whether the entry being looked up is a file
512 or a directory. Instances of this class can morph into either
513 Dir or File objects by a later, more precise lookup.
514
515 Note: this class does not define __cmp__ and __hash__ for
516 efficiency reasons. SCons does a lot of comparing of
517 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
518 as fast as possible, which means we want to use Python's built-in
519 object identity comparisons.
520 """
521
522 memoizer_counters = []
523
524 - def __init__(self, name, directory, fs):
525 """Initialize a generic Node.FS.Base object.
526
527 Call the superclass initialization, take care of setting up
528 our relative and absolute paths, identify our parent
529 directory, and indicate that this node should use
530 signatures."""
531 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
532 SCons.Node.Node.__init__(self)
533
534
535
536 self.name = SCons.Util.silent_intern(name)
537 self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1])
538 self.fs = fs
539
540 assert directory, "A directory must be provided"
541
542 self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name))
543 self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name))
544 if directory.path == '.':
545 self.path = SCons.Util.silent_intern(name)
546 else:
547 self.path = SCons.Util.silent_intern(directory.entry_path(name))
548 if directory.tpath == '.':
549 self.tpath = SCons.Util.silent_intern(name)
550 else:
551 self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name))
552 self.path_elements = directory.path_elements + [self]
553
554 self.dir = directory
555 self.cwd = None
556 self.duplicate = directory.duplicate
557
559 return '"' + self.__str__() + '"'
560
562 """
563 This node, which already existed, is being looked up as the
564 specified klass. Raise an exception if it isn't.
565 """
566 if isinstance(self, klass) or klass is Entry:
567 return
568 raise TypeError("Tried to lookup %s '%s' as a %s." %\
569 (self.__class__.__name__, self.path, klass.__name__))
570
573
576
579
587
588 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
589
591 try:
592 return self._memo['_save_str']
593 except KeyError:
594 pass
595 result = sys.intern(self._get_str())
596 self._memo['_save_str'] = result
597 return result
598
623
624 rstr = __str__
625
626 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
627
629 try: return self._memo['stat']
630 except KeyError: pass
631 try: result = self.fs.stat(self.abspath)
632 except os.error: result = None
633 self._memo['stat'] = result
634 return result
635
637 return self.stat() is not None
638
641
643 st = self.stat()
644 if st: return st[stat.ST_MTIME]
645 else: return None
646
648 st = self.stat()
649 if st: return st[stat.ST_SIZE]
650 else: return None
651
653 st = self.stat()
654 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
655
657 st = self.stat()
658 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
659
660 if hasattr(os, 'symlink'):
662 try: st = self.fs.lstat(self.abspath)
663 except os.error: return 0
664 return stat.S_ISLNK(st[stat.ST_MODE])
665 else:
668
670 if self is dir:
671 return 1
672 else:
673 return self.dir.is_under(dir)
674
677
689
691 """Return path relative to the current working directory of the
692 Node.FS.Base object that owns us."""
693 if not dir:
694 dir = self.fs.getcwd()
695 if self == dir:
696 return '.'
697 path_elems = self.path_elements
698 try: i = path_elems.index(dir)
699 except ValueError: pass
700 else: path_elems = path_elems[i+1:]
701 path_elems = [n.name for n in path_elems]
702 return os.sep.join(path_elems)
703
705 """Set the source code builder for this node."""
706 self.sbuilder = builder
707 if not self.has_builder():
708 self.builder_set(builder)
709
711 """Fetch the source code builder for this node.
712
713 If there isn't one, we cache the source code builder specified
714 for the directory (which in turn will cache the value from its
715 parent directory, and so on up to the file system root).
716 """
717 try:
718 scb = self.sbuilder
719 except AttributeError:
720 scb = self.dir.src_builder()
721 self.sbuilder = scb
722 return scb
723
725 """Get the absolute path of the file."""
726 return self.abspath
727
729
730
731
732 return self.name
733
735 try:
736 return self._proxy
737 except AttributeError:
738 ret = EntryProxy(self)
739 self._proxy = ret
740 return ret
741
743 """
744
745 Generates a target entry that corresponds to this entry (usually
746 a source file) with the specified prefix and suffix.
747
748 Note that this method can be overridden dynamically for generated
749 files that need different behavior. See Tool/swig.py for
750 an example.
751 """
752 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
753
756
757 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
758
760 """
761 Return all of the directories for a given path list, including
762 corresponding "backing" directories in any repositories.
763
764 The Node lookups are relative to this Node (typically a
765 directory), so memoizing result saves cycles from looking
766 up the same path for each target in a given directory.
767 """
768 try:
769 memo_dict = self._memo['Rfindalldirs']
770 except KeyError:
771 memo_dict = {}
772 self._memo['Rfindalldirs'] = memo_dict
773 else:
774 try:
775 return memo_dict[pathlist]
776 except KeyError:
777 pass
778
779 create_dir_relative_to_self = self.Dir
780 result = []
781 for path in pathlist:
782 if isinstance(path, SCons.Node.Node):
783 result.append(path)
784 else:
785 dir = create_dir_relative_to_self(path)
786 result.extend(dir.get_all_rdirs())
787
788 memo_dict[pathlist] = result
789
790 return result
791
792 - def RDirs(self, pathlist):
793 """Search for a list of directories in the Repository list."""
794 cwd = self.cwd or self.fs._cwd
795 return cwd.Rfindalldirs(pathlist)
796
797 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
798
800 try:
801 return self._memo['rentry']
802 except KeyError:
803 pass
804 result = self
805 if not self.exists():
806 norm_name = _my_normcase(self.name)
807 for dir in self.dir.get_all_rdirs():
808 try:
809 node = dir.entries[norm_name]
810 except KeyError:
811 if dir.entry_exists_on_disk(self.name):
812 result = dir.Entry(self.name)
813 break
814 self._memo['rentry'] = result
815 return result
816
817 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
819
821 """This is the class for generic Node.FS entries--that is, things
822 that could be a File or a Dir, but we're just not sure yet.
823 Consequently, the methods in this class really exist just to
824 transform their associated object into the right class when the
825 time comes, and then call the same-named method in the transformed
826 class."""
827
828 - def diskcheck_match(self):
830
831 - def disambiguate(self, must_exist=None):
832 """
833 """
834 if self.isdir():
835 self.__class__ = Dir
836 self._morph()
837 elif self.isfile():
838 self.__class__ = File
839 self._morph()
840 self.clear()
841 else:
842
843
844
845
846
847
848
849
850
851 srcdir = self.dir.srcnode()
852 if srcdir != self.dir and \
853 srcdir.entry_exists_on_disk(self.name) and \
854 self.srcnode().isdir():
855 self.__class__ = Dir
856 self._morph()
857 elif must_exist:
858 msg = "No such file or directory: '%s'" % self.abspath
859 raise SCons.Errors.UserError(msg)
860 else:
861 self.__class__ = File
862 self._morph()
863 self.clear()
864 return self
865
867 """We're a generic Entry, but the caller is actually looking for
868 a File at this point, so morph into one."""
869 self.__class__ = File
870 self._morph()
871 self.clear()
872 return File.rfile(self)
873
874 - def scanner_key(self):
875 return self.get_suffix()
876
877 - def get_contents(self):
878 """Fetch the contents of the entry. Returns the exact binary
879 contents of the file."""
880 try:
881 self = self.disambiguate(must_exist=1)
882 except SCons.Errors.UserError:
883
884
885
886
887
888 return ''
889 else:
890 return self.get_contents()
891
893 """Fetch the decoded text contents of a Unicode encoded Entry.
894
895 Since this should return the text contents from the file
896 system, we check to see into what sort of subclass we should
897 morph this Entry."""
898 try:
899 self = self.disambiguate(must_exist=1)
900 except SCons.Errors.UserError:
901
902
903
904
905
906 return ''
907 else:
908 return self.get_text_contents()
909
910 - def must_be_same(self, klass):
911 """Called to make sure a Node is a Dir. Since we're an
912 Entry, we can morph into one."""
913 if self.__class__ is not klass:
914 self.__class__ = klass
915 self._morph()
916 self.clear()
917
918
919
920
921
922
923
924
925
926
927
928
930 """Return if the Entry exists. Check the file system to see
931 what we should turn into first. Assume a file if there's no
932 directory."""
933 return self.disambiguate().exists()
934
935 - def rel_path(self, other):
936 d = self.disambiguate()
937 if d.__class__ is Entry:
938 raise Exception("rel_path() could not disambiguate File/Dir")
939 return d.rel_path(other)
940
941 - def new_ninfo(self):
942 return self.disambiguate().new_ninfo()
943
944 - def changed_since_last_build(self, target, prev_ni):
945 return self.disambiguate().changed_since_last_build(target, prev_ni)
946
947 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
948 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
949
950 - def get_subst_proxy(self):
952
953
954
955 _classEntry = Entry
956
957
959
960 if SCons.Memoize.use_memoizer:
961 __metaclass__ = SCons.Memoize.Memoized_Metaclass
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979 - def chmod(self, path, mode):
981 - def copy(self, src, dst):
982 return shutil.copy(src, dst)
983 - def copy2(self, src, dst):
984 return shutil.copy2(src, dst)
995 - def link(self, src, dst):
1007 - def stat(self, path):
1011 - def open(self, path):
1015
1016 if hasattr(os, 'symlink'):
1019 else:
1022
1023 if hasattr(os, 'readlink'):
1026 else:
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1041
1042 memoizer_counters = []
1043
1045 """Initialize the Node.FS subsystem.
1046
1047 The supplied path is the top of the source tree, where we
1048 expect to find the top-level build file. If no path is
1049 supplied, the current directory is the default.
1050
1051 The path argument must be a valid absolute path.
1052 """
1053 if __debug__: logInstanceCreation(self, 'Node.FS')
1054
1055 self._memo = {}
1056
1057 self.Root = {}
1058 self.SConstruct_dir = None
1059 self.max_drift = default_max_drift
1060
1061 self.Top = None
1062 if path is None:
1063 self.pathTop = os.getcwd()
1064 else:
1065 self.pathTop = path
1066 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1067
1068 self.Top = self.Dir(self.pathTop)
1069 self.Top.path = '.'
1070 self.Top.tpath = '.'
1071 self._cwd = self.Top
1072
1073 DirNodeInfo.fs = self
1074 FileNodeInfo.fs = self
1075
1077 self.SConstruct_dir = dir
1078
1080 return self.max_drift
1081
1083 self.max_drift = max_drift
1084
1087
1088 - def chdir(self, dir, change_os_dir=0):
1089 """Change the current working directory for lookups.
1090 If change_os_dir is true, we will also change the "real" cwd
1091 to match.
1092 """
1093 curr=self._cwd
1094 try:
1095 if dir is not None:
1096 self._cwd = dir
1097 if change_os_dir:
1098 os.chdir(dir.abspath)
1099 except OSError:
1100 self._cwd = curr
1101 raise
1102
1104 """
1105 Returns the root directory for the specified drive, creating
1106 it if necessary.
1107 """
1108 drive = _my_normcase(drive)
1109 try:
1110 return self.Root[drive]
1111 except KeyError:
1112 root = RootDir(drive, self)
1113 self.Root[drive] = root
1114 if not drive:
1115 self.Root[self.defaultDrive] = root
1116 elif drive == self.defaultDrive:
1117 self.Root[''] = root
1118 return root
1119
1120 - def _lookup(self, p, directory, fsclass, create=1):
1121 """
1122 The generic entry point for Node lookup with user-supplied data.
1123
1124 This translates arbitrary input into a canonical Node.FS object
1125 of the specified fsclass. The general approach for strings is
1126 to turn it into a fully normalized absolute path and then call
1127 the root directory's lookup_abs() method for the heavy lifting.
1128
1129 If the path name begins with '#', it is unconditionally
1130 interpreted relative to the top-level directory of this FS. '#'
1131 is treated as a synonym for the top-level SConstruct directory,
1132 much like '~' is treated as a synonym for the user's home
1133 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1134 to the 'foo' subdirectory underneath the top-level SConstruct
1135 directory.
1136
1137 If the path name is relative, then the path is looked up relative
1138 to the specified directory, or the current directory (self._cwd,
1139 typically the SConscript directory) if the specified directory
1140 is None.
1141 """
1142 if isinstance(p, Base):
1143
1144
1145 p.must_be_same(fsclass)
1146 return p
1147
1148 p = str(p)
1149
1150 initial_hash = (p[0:1] == '#')
1151 if initial_hash:
1152
1153
1154
1155 p = p[1:]
1156 directory = self.Top
1157
1158 if directory and not isinstance(directory, Dir):
1159 directory = self.Dir(directory)
1160
1161 if do_splitdrive:
1162 drive, p = os.path.splitdrive(p)
1163 else:
1164 drive = ''
1165 if drive and not p:
1166
1167
1168 p = os.sep
1169 absolute = os.path.isabs(p)
1170
1171 needs_normpath = needs_normpath_check.match(p)
1172
1173 if initial_hash or not absolute:
1174
1175
1176
1177
1178
1179 if not directory:
1180 directory = self._cwd
1181 if p:
1182 p = directory.labspath + '/' + p
1183 else:
1184 p = directory.labspath
1185
1186 if needs_normpath:
1187 p = os.path.normpath(p)
1188
1189 if drive or absolute:
1190 root = self.get_root(drive)
1191 else:
1192 if not directory:
1193 directory = self._cwd
1194 root = directory.root
1195
1196 if os.sep != '/':
1197 p = p.replace(os.sep, '/')
1198 return root._lookup_abs(p, fsclass, create)
1199
1200 - def Entry(self, name, directory = None, create = 1):
1201 """Look up or create a generic Entry node with the specified name.
1202 If the name is a relative path (begins with ./, ../, or a file
1203 name), then it is looked up relative to the supplied directory
1204 node, or to the top level directory of the FS (supplied at
1205 construction time) if no directory is supplied.
1206 """
1207 return self._lookup(name, directory, Entry, create)
1208
1209 - def File(self, name, directory = None, create = 1):
1210 """Look up or create a File node with the specified name. If
1211 the name is a relative path (begins with ./, ../, or a file name),
1212 then it is looked up relative to the supplied directory node,
1213 or to the top level directory of the FS (supplied at construction
1214 time) if no directory is supplied.
1215
1216 This method will raise TypeError if a directory is found at the
1217 specified path.
1218 """
1219 return self._lookup(name, directory, File, create)
1220
1221 - def Dir(self, name, directory = None, create = True):
1222 """Look up or create a Dir node with the specified name. If
1223 the name is a relative path (begins with ./, ../, or a file name),
1224 then it is looked up relative to the supplied directory node,
1225 or to the top level directory of the FS (supplied at construction
1226 time) if no directory is supplied.
1227
1228 This method will raise TypeError if a normal file is found at the
1229 specified path.
1230 """
1231 return self._lookup(name, directory, Dir, create)
1232
1233 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1234 """Link the supplied variant directory to the source directory
1235 for purposes of building files."""
1236
1237 if not isinstance(src_dir, SCons.Node.Node):
1238 src_dir = self.Dir(src_dir)
1239 if not isinstance(variant_dir, SCons.Node.Node):
1240 variant_dir = self.Dir(variant_dir)
1241 if src_dir.is_under(variant_dir):
1242 raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
1243 if variant_dir.srcdir:
1244 if variant_dir.srcdir == src_dir:
1245 return
1246 raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
1247 variant_dir.link(src_dir, duplicate)
1248
1255
1257 """Create targets in corresponding variant directories
1258
1259 Climb the directory tree, and look up path names
1260 relative to any linked variant directories we find.
1261
1262 Even though this loops and walks up the tree, we don't memoize
1263 the return value because this is really only used to process
1264 the command-line targets.
1265 """
1266 targets = []
1267 message = None
1268 fmt = "building associated VariantDir targets: %s"
1269 start_dir = dir
1270 while dir:
1271 for bd in dir.variant_dirs:
1272 if start_dir.is_under(bd):
1273
1274 return [orig], fmt % str(orig)
1275 p = os.path.join(bd.path, *tail)
1276 targets.append(self.Entry(p))
1277 tail = [dir.name] + tail
1278 dir = dir.up()
1279 if targets:
1280 message = fmt % ' '.join(map(str, targets))
1281 return targets, message
1282
1283 - def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1284 """
1285 Globs
1286
1287 This is mainly a shim layer
1288 """
1289 if cwd is None:
1290 cwd = self.getcwd()
1291 return cwd.glob(pathname, ondisk, source, strings)
1292
1309
1312
1313 glob_magic_check = re.compile('[*?[]')
1314
1317
1319 """A class for directories in a file system.
1320 """
1321
1322 memoizer_counters = []
1323
1324 NodeInfo = DirNodeInfo
1325 BuildInfo = DirBuildInfo
1326
1327 - def __init__(self, name, directory, fs):
1331
1333 """Turn a file system Node (either a freshly initialized directory
1334 object or a separate Entry object) into a proper directory object.
1335
1336 Set up this directory's entries and hook it into the file
1337 system tree. Specify that directories (this Node) don't use
1338 signatures for calculating whether they're current.
1339 """
1340
1341 self.repositories = []
1342 self.srcdir = None
1343
1344 self.entries = {}
1345 self.entries['.'] = self
1346 self.entries['..'] = self.dir
1347 self.cwd = self
1348 self.searched = 0
1349 self._sconsign = None
1350 self.variant_dirs = []
1351 self.root = self.dir.root
1352
1353
1354
1355
1356 self.builder = get_MkdirBuilder()
1357 self.get_executor().set_action_list(self.builder.action)
1358
1362
1364 """Called when we change the repository(ies) for a directory.
1365 This clears any cached information that is invalidated by changing
1366 the repository."""
1367
1368 for node in self.entries.values():
1369 if node != self.dir:
1370 if node != self and isinstance(node, Dir):
1371 node.__clearRepositoryCache(duplicate)
1372 else:
1373 node.clear()
1374 try:
1375 del node._srcreps
1376 except AttributeError:
1377 pass
1378 if duplicate is not None:
1379 node.duplicate=duplicate
1380
1382 if node != self:
1383 node.duplicate = node.get_dir().duplicate
1384
1385 - def Entry(self, name):
1386 """
1387 Looks up or creates an entry node named 'name' relative to
1388 this directory.
1389 """
1390 return self.fs.Entry(name, self)
1391
1392 - def Dir(self, name, create=True):
1393 """
1394 Looks up or creates a directory node named 'name' relative to
1395 this directory.
1396 """
1397 return self.fs.Dir(name, self, create)
1398
1399 - def File(self, name):
1400 """
1401 Looks up or creates a file node named 'name' relative to
1402 this directory.
1403 """
1404 return self.fs.File(name, self)
1405
1407 """
1408 Looks up a *normalized* relative path name, relative to this
1409 directory.
1410
1411 This method is intended for use by internal lookups with
1412 already-normalized path data. For general-purpose lookups,
1413 use the Entry(), Dir() and File() methods above.
1414
1415 This method does *no* input checking and will die or give
1416 incorrect results if it's passed a non-normalized path name (e.g.,
1417 a path containing '..'), an absolute path name, a top-relative
1418 ('#foo') path name, or any kind of object.
1419 """
1420 name = self.entry_labspath(name)
1421 return self.root._lookup_abs(name, klass, create)
1422
1423 - def link(self, srcdir, duplicate):
1424 """Set this directory as the variant directory for the
1425 supplied source directory."""
1426 self.srcdir = srcdir
1427 self.duplicate = duplicate
1428 self.__clearRepositoryCache(duplicate)
1429 srcdir.variant_dirs.append(self)
1430
1432 """Returns a list of repositories for this directory.
1433 """
1434 if self.srcdir and not self.duplicate:
1435 return self.srcdir.get_all_rdirs() + self.repositories
1436 return self.repositories
1437
1438 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1439
1441 try:
1442 return list(self._memo['get_all_rdirs'])
1443 except KeyError:
1444 pass
1445
1446 result = [self]
1447 fname = '.'
1448 dir = self
1449 while dir:
1450 for rep in dir.getRepositories():
1451 result.append(rep.Dir(fname))
1452 if fname == '.':
1453 fname = dir.name
1454 else:
1455 fname = dir.name + os.sep + fname
1456 dir = dir.up()
1457
1458 self._memo['get_all_rdirs'] = list(result)
1459
1460 return result
1461
1463 if dir != self and not dir in self.repositories:
1464 self.repositories.append(dir)
1465 dir.tpath = '.'
1466 self.__clearRepositoryCache()
1467
1469 return self.entries['..']
1470
1473
1474 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1475
1477 """Return a path to "other" relative to this directory.
1478 """
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490 try:
1491 memo_dict = self._memo['rel_path']
1492 except KeyError:
1493 memo_dict = {}
1494 self._memo['rel_path'] = memo_dict
1495 else:
1496 try:
1497 return memo_dict[other]
1498 except KeyError:
1499 pass
1500
1501 if self is other:
1502 result = '.'
1503
1504 elif not other in self.path_elements:
1505 try:
1506 other_dir = other.get_dir()
1507 except AttributeError:
1508 result = str(other)
1509 else:
1510 if other_dir is None:
1511 result = other.name
1512 else:
1513 dir_rel_path = self.rel_path(other_dir)
1514 if dir_rel_path == '.':
1515 result = other.name
1516 else:
1517 result = dir_rel_path + os.sep + other.name
1518 else:
1519 i = self.path_elements.index(other) + 1
1520
1521 path_elems = ['..'] * (len(self.path_elements) - i) \
1522 + [n.name for n in other.path_elements[i:]]
1523
1524 result = os.sep.join(path_elems)
1525
1526 memo_dict[other] = result
1527
1528 return result
1529
1533
1537
1539 """Return this directory's implicit dependencies.
1540
1541 We don't bother caching the results because the scan typically
1542 shouldn't be requested more than once (as opposed to scanning
1543 .h file contents, which can be requested as many times as the
1544 files is #included by other files).
1545 """
1546 if not scanner:
1547 return []
1548
1549
1550
1551
1552
1553
1554
1555
1556 self.clear()
1557 return scanner(self, env, path)
1558
1559
1560
1561
1562
1565
1571
1572
1573
1574
1575
1577 """Create this directory, silently and without worrying about
1578 whether the builder is the default or not."""
1579 listDirs = []
1580 parent = self
1581 while parent:
1582 if parent.exists():
1583 break
1584 listDirs.append(parent)
1585 p = parent.up()
1586 if p is None:
1587
1588
1589 raise SCons.Errors.StopError(parent.path)
1590 parent = p
1591 listDirs.reverse()
1592 for dirnode in listDirs:
1593 try:
1594
1595
1596
1597
1598 SCons.Node.Node.build(dirnode)
1599 dirnode.get_executor().nullify()
1600
1601
1602
1603
1604 dirnode.clear()
1605 except OSError:
1606 pass
1607
1611
1613 """Return any corresponding targets in a variant directory.
1614 """
1615 return self.fs.variant_dir_target_climb(self, self, [])
1616
1618 """A directory does not get scanned."""
1619 return None
1620
1622 """We already emit things in text, so just return the binary
1623 version."""
1624 return self.get_contents()
1625
1626 - def get_contents(self):
1627 """Return content signatures and names of all our children
1628 separated by new-lines. Ensure that the nodes are sorted."""
1629 contents = []
1630 for node in sorted(self.children(), key=lambda t: t.name):
1631 contents.append('%s %s\n' % (node.get_csig(), node.name))
1632 return ''.join(contents)
1633
1635 """Compute the content signature for Directory nodes. In
1636 general, this is not needed and the content signature is not
1637 stored in the DirNodeInfo. However, if get_contents on a Dir
1638 node is called which has a child directory, the child
1639 directory should return the hash of its contents."""
1640 contents = self.get_contents()
1641 return SCons.Util.MD5signature(contents)
1642
1645
1646 changed_since_last_build = SCons.Node.Node.state_has_changed
1647
1658
1660 if not self.exists():
1661 norm_name = _my_normcase(self.name)
1662 for dir in self.dir.get_all_rdirs():
1663 try: node = dir.entries[norm_name]
1664 except KeyError: node = dir.dir_on_disk(self.name)
1665 if node and node.exists() and \
1666 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1667 return node
1668 return self
1669
1671 """Return the .sconsign file info for this directory,
1672 creating it first if necessary."""
1673 if not self._sconsign:
1674 import SCons.SConsign
1675 self._sconsign = SCons.SConsign.ForDirectory(self)
1676 return self._sconsign
1677
1679 """Dir has a special need for srcnode()...if we
1680 have a srcdir attribute set, then that *is* our srcnode."""
1681 if self.srcdir:
1682 return self.srcdir
1683 return Base.srcnode(self)
1684
1686 """Return the latest timestamp from among our children"""
1687 stamp = 0
1688 for kid in self.children():
1689 if kid.get_timestamp() > stamp:
1690 stamp = kid.get_timestamp()
1691 return stamp
1692
1693 - def entry_abspath(self, name):
1694 return self.abspath + os.sep + name
1695
1696 - def entry_labspath(self, name):
1697 return self.labspath + '/' + name
1698
1699 - def entry_path(self, name):
1700 return self.path + os.sep + name
1701
1702 - def entry_tpath(self, name):
1703 return self.tpath + os.sep + name
1704
1705 - def entry_exists_on_disk(self, name):
1706 try:
1707 d = self.on_disk_entries
1708 except AttributeError:
1709 d = {}
1710 try:
1711 entries = os.listdir(self.abspath)
1712 except OSError:
1713 pass
1714 else:
1715 for entry in map(_my_normcase, entries):
1716 d[entry] = True
1717 self.on_disk_entries = d
1718 if sys.platform == 'win32':
1719 name = _my_normcase(name)
1720 result = d.get(name)
1721 if result is None:
1722
1723
1724 result = os.path.exists(self.abspath + os.sep + name)
1725 d[name] = result
1726 return result
1727 else:
1728 return name in d
1729
1730 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1731
1733 try:
1734 return self._memo['srcdir_list']
1735 except KeyError:
1736 pass
1737
1738 result = []
1739
1740 dirname = '.'
1741 dir = self
1742 while dir:
1743 if dir.srcdir:
1744 result.append(dir.srcdir.Dir(dirname))
1745 dirname = dir.name + os.sep + dirname
1746 dir = dir.up()
1747
1748 self._memo['srcdir_list'] = result
1749
1750 return result
1751
1768
1771
1772 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1773
1775 try:
1776 memo_dict = self._memo['srcdir_find_file']
1777 except KeyError:
1778 memo_dict = {}
1779 self._memo['srcdir_find_file'] = memo_dict
1780 else:
1781 try:
1782 return memo_dict[filename]
1783 except KeyError:
1784 pass
1785
1786 def func(node):
1787 if (isinstance(node, File) or isinstance(node, Entry)) and \
1788 (node.is_derived() or node.exists()):
1789 return node
1790 return None
1791
1792 norm_name = _my_normcase(filename)
1793
1794 for rdir in self.get_all_rdirs():
1795 try: node = rdir.entries[norm_name]
1796 except KeyError: node = rdir.file_on_disk(filename)
1797 else: node = func(node)
1798 if node:
1799 result = (node, self)
1800 memo_dict[filename] = result
1801 return result
1802
1803 for srcdir in self.srcdir_list():
1804 for rdir in srcdir.get_all_rdirs():
1805 try: node = rdir.entries[norm_name]
1806 except KeyError: node = rdir.file_on_disk(filename)
1807 else: node = func(node)
1808 if node:
1809 result = (File(filename, self, self.fs), srcdir)
1810 memo_dict[filename] = result
1811 return result
1812
1813 result = (None, None)
1814 memo_dict[filename] = result
1815 return result
1816
1825
1836
1837 - def walk(self, func, arg):
1838 """
1839 Walk this directory tree by calling the specified function
1840 for each directory in the tree.
1841
1842 This behaves like the os.path.walk() function, but for in-memory
1843 Node.FS.Dir objects. The function takes the same arguments as
1844 the functions passed to os.path.walk():
1845
1846 func(arg, dirname, fnames)
1847
1848 Except that "dirname" will actually be the directory *Node*,
1849 not the string. The '.' and '..' entries are excluded from
1850 fnames. The fnames list may be modified in-place to filter the
1851 subdirectories visited or otherwise impose a specific order.
1852 The "arg" argument is always passed to func() and may be used
1853 in any way (or ignored, passing None is common).
1854 """
1855 entries = self.entries
1856 names = list(entries.keys())
1857 names.remove('.')
1858 names.remove('..')
1859 func(arg, self, names)
1860 for dirname in [n for n in names if isinstance(entries[n], Dir)]:
1861 entries[dirname].walk(func, arg)
1862
1863 - def glob(self, pathname, ondisk=True, source=False, strings=False):
1864 """
1865 Returns a list of Nodes (or strings) matching a specified
1866 pathname pattern.
1867
1868 Pathname patterns follow UNIX shell semantics: * matches
1869 any-length strings of any characters, ? matches any character,
1870 and [] can enclose lists or ranges of characters. Matches do
1871 not span directory separators.
1872
1873 The matches take into account Repositories, returning local
1874 Nodes if a corresponding entry exists in a Repository (either
1875 an in-memory Node or something on disk).
1876
1877 By defafult, the glob() function matches entries that exist
1878 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1879 argument to False (or some other non-true value) causes the glob()
1880 function to only match in-memory Nodes. The default behavior is
1881 to return both the on-disk and in-memory Nodes.
1882
1883 The "source" argument, when true, specifies that corresponding
1884 source Nodes must be returned if you're globbing in a build
1885 directory (initialized with VariantDir()). The default behavior
1886 is to return Nodes local to the VariantDir().
1887
1888 The "strings" argument, when true, returns the matches as strings,
1889 not Nodes. The strings are path names relative to this directory.
1890
1891 The underlying algorithm is adapted from the glob.glob() function
1892 in the Python library (but heavily modified), and uses fnmatch()
1893 under the covers.
1894 """
1895 dirname, basename = os.path.split(pathname)
1896 if not dirname:
1897 return sorted(self._glob1(basename, ondisk, source, strings),
1898 key=lambda t: str(t))
1899 if has_glob_magic(dirname):
1900 list = self.glob(dirname, ondisk, source, strings=False)
1901 else:
1902 list = [self.Dir(dirname, create=True)]
1903 result = []
1904 for dir in list:
1905 r = dir._glob1(basename, ondisk, source, strings)
1906 if strings:
1907 r = [os.path.join(str(dir), x) for x in r]
1908 result.extend(r)
1909 return sorted(result, key=lambda a: str(a))
1910
1911 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1912 """
1913 Globs for and returns a list of entry names matching a single
1914 pattern in this directory.
1915
1916 This searches any repositories and source directories for
1917 corresponding entries and returns a Node (or string) relative
1918 to the current directory if an entry is found anywhere.
1919
1920 TODO: handle pattern with no wildcard
1921 """
1922 search_dir_list = self.get_all_rdirs()
1923 for srcdir in self.srcdir_list():
1924 search_dir_list.extend(srcdir.get_all_rdirs())
1925
1926 selfEntry = self.Entry
1927 names = []
1928 for dir in search_dir_list:
1929
1930
1931
1932 node_names = [ v.name for k, v in dir.entries.items()
1933 if k not in ('.', '..') ]
1934 names.extend(node_names)
1935 if not strings:
1936
1937
1938 for name in node_names: selfEntry(name)
1939 if ondisk:
1940 try:
1941 disk_names = os.listdir(dir.abspath)
1942 except os.error:
1943 continue
1944 names.extend(disk_names)
1945 if not strings:
1946
1947
1948
1949
1950
1951
1952
1953
1954 if pattern[0] != '.':
1955
1956 disk_names = [x for x in disk_names if x[0] != '.']
1957 disk_names = fnmatch.filter(disk_names, pattern)
1958 dirEntry = dir.Entry
1959 for name in disk_names:
1960
1961
1962 name = './' + name
1963 node = dirEntry(name).disambiguate()
1964 n = selfEntry(name)
1965 if n.__class__ != node.__class__:
1966 n.__class__ = node.__class__
1967 n._morph()
1968
1969 names = set(names)
1970 if pattern[0] != '.':
1971
1972 names = [x for x in names if x[0] != '.']
1973 names = fnmatch.filter(names, pattern)
1974
1975 if strings:
1976 return names
1977
1978
1979 return [self.entries[_my_normcase(n)] for n in names]
1980
1982 """A class for the root directory of a file system.
1983
1984 This is the same as a Dir class, except that the path separator
1985 ('/' or '\\') is actually part of the name, so we don't need to
1986 add a separator when creating the path names of entries within
1987 this directory.
1988 """
1990 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1991
1992
1993
1994 self.abspath = ''
1995 self.labspath = ''
1996 self.path = ''
1997 self.tpath = ''
1998 self.path_elements = []
1999 self.duplicate = 0
2000 self.root = self
2001 Base.__init__(self, name, self, fs)
2002
2003
2004
2005
2006
2007 self.abspath = name + os.sep
2008 self.labspath = ''
2009 self.path = name + os.sep
2010 self.tpath = name + os.sep
2011 self._morph()
2012
2013 self._lookupDict = {}
2014
2015
2016
2017
2018
2019 self._lookupDict[''] = self
2020 self._lookupDict['/'] = self
2021 self._lookupDict['//'] = self
2022 self._lookupDict[os.sep] = self
2023 self._lookupDict[os.sep + os.sep] = self
2024
2029
2031 """
2032 Fast (?) lookup of a *normalized* absolute path.
2033
2034 This method is intended for use by internal lookups with
2035 already-normalized path data. For general-purpose lookups,
2036 use the FS.Entry(), FS.Dir() or FS.File() methods.
2037
2038 The caller is responsible for making sure we're passed a
2039 normalized absolute path; we merely let Python's dictionary look
2040 up and return the One True Node.FS object for the path.
2041
2042 If no Node for the specified "p" doesn't already exist, and
2043 "create" is specified, the Node may be created after recursive
2044 invocation to find or create the parent directory or directories.
2045 """
2046 k = _my_normcase(p)
2047 try:
2048 result = self._lookupDict[k]
2049 except KeyError:
2050 if not create:
2051 msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2052 raise SCons.Errors.UserError(msg)
2053
2054
2055 dir_name, file_name = os.path.split(p)
2056 dir_node = self._lookup_abs(dir_name, Dir)
2057 result = klass(file_name, dir_node, self.fs)
2058
2059
2060
2061 result.diskcheck_match()
2062
2063 self._lookupDict[k] = result
2064 dir_node.entries[_my_normcase(file_name)] = result
2065 dir_node.implicit = None
2066 else:
2067
2068
2069 result.must_be_same(klass)
2070 return result
2071
2074
2075 - def entry_abspath(self, name):
2076 return self.abspath + name
2077
2078 - def entry_labspath(self, name):
2080
2081 - def entry_path(self, name):
2082 return self.path + name
2083
2084 - def entry_tpath(self, name):
2085 return self.tpath + name
2086
2088 if self is dir:
2089 return 1
2090 else:
2091 return 0
2092
2095
2098
2101
2120
2122 current_version_id = 1
2123
2125 """
2126 Converts this FileBuildInfo object for writing to a .sconsign file
2127
2128 This replaces each Node in our various dependency lists with its
2129 usual string representation: relative to the top-level SConstruct
2130 directory, or an absolute path if it's outside.
2131 """
2132 if os.sep == '/':
2133 node_to_str = str
2134 else:
2135 def node_to_str(n):
2136 try:
2137 s = n.path
2138 except AttributeError:
2139 s = str(n)
2140 else:
2141 s = s.replace(os.sep, '/')
2142 return s
2143 for attr in ['bsources', 'bdepends', 'bimplicit']:
2144 try:
2145 val = getattr(self, attr)
2146 except AttributeError:
2147 pass
2148 else:
2149 setattr(self, attr, list(map(node_to_str, val)))
2151 """
2152 Converts a newly-read FileBuildInfo object for in-SCons use
2153
2154 For normal up-to-date checking, we don't have any conversion to
2155 perform--but we're leaving this method here to make that clear.
2156 """
2157 pass
2159 """
2160 Prepares a FileBuildInfo object for explaining what changed
2161
2162 The bsources, bdepends and bimplicit lists have all been
2163 stored on disk as paths relative to the top-level SConstruct
2164 directory. Convert the strings to actual Nodes (for use by the
2165 --debug=explain code and --implicit-cache).
2166 """
2167 attrs = [
2168 ('bsources', 'bsourcesigs'),
2169 ('bdepends', 'bdependsigs'),
2170 ('bimplicit', 'bimplicitsigs'),
2171 ]
2172 for (nattr, sattr) in attrs:
2173 try:
2174 strings = getattr(self, nattr)
2175 nodeinfos = getattr(self, sattr)
2176 except AttributeError:
2177 continue
2178 nodes = []
2179 for s, ni in zip(strings, nodeinfos):
2180 if not isinstance(s, SCons.Node.Node):
2181 s = ni.str_to_node(s)
2182 nodes.append(s)
2183 setattr(self, nattr, nodes)
2193
2195 """A class for files in a file system.
2196 """
2197
2198 memoizer_counters = []
2199
2200 NodeInfo = FileNodeInfo
2201 BuildInfo = FileBuildInfo
2202
2203 md5_chunksize = 64
2204
2208
2209 - def __init__(self, name, directory, fs):
2213
2214 - def Entry(self, name):
2215 """Create an entry node named 'name' relative to
2216 the directory of this file."""
2217 return self.dir.Entry(name)
2218
2219 - def Dir(self, name, create=True):
2220 """Create a directory node named 'name' relative to
2221 the directory of this file."""
2222 return self.dir.Dir(name, create=create)
2223
2224 - def Dirs(self, pathlist):
2225 """Create a list of directories relative to the SConscript
2226 directory of this file."""
2227 return [self.Dir(p) for p in pathlist]
2228
2229 - def File(self, name):
2230 """Create a file node named 'name' relative to
2231 the directory of this file."""
2232 return self.dir.File(name)
2233
2234
2235
2236
2237
2238
2239
2240
2242 """Turn a file system node into a File object."""
2243 self.scanner_paths = {}
2244 if not hasattr(self, '_local'):
2245 self._local = 0
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257 if self.has_builder():
2258 self.changed_since_last_build = self.decide_target
2259
2262
2263 - def get_contents(self):
2264 if not self.rexists():
2265 return ''
2266 fname = self.rfile().abspath
2267 try:
2268 contents = open(fname, "rb").read()
2269 except EnvironmentError, e:
2270 if not e.filename:
2271 e.filename = fname
2272 raise
2273 return contents
2274
2275
2276
2277
2279 contents = self.get_contents()
2280
2281
2282
2283
2284
2285
2286 if contents.startswith(codecs.BOM_UTF8):
2287 return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2288 if contents.startswith(codecs.BOM_UTF16_LE):
2289 return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2290 if contents.startswith(codecs.BOM_UTF16_BE):
2291 return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2292 return contents
2293
2294 - def get_content_hash(self):
2295 """
2296 Compute and return the MD5 hash for this file.
2297 """
2298 if not self.rexists():
2299 return SCons.Util.MD5signature('')
2300 fname = self.rfile().abspath
2301 try:
2302 cs = SCons.Util.MD5filesignature(fname,
2303 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2304 except EnvironmentError, e:
2305 if not e.filename:
2306 e.filename = fname
2307 raise
2308 return cs
2309
2310
2311 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2312
2314 try:
2315 return self._memo['get_size']
2316 except KeyError:
2317 pass
2318
2319 if self.rexists():
2320 size = self.rfile().getsize()
2321 else:
2322 size = 0
2323
2324 self._memo['get_size'] = size
2325
2326 return size
2327
2328 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2329
2331 try:
2332 return self._memo['get_timestamp']
2333 except KeyError:
2334 pass
2335
2336 if self.rexists():
2337 timestamp = self.rfile().getmtime()
2338 else:
2339 timestamp = 0
2340
2341 self._memo['get_timestamp'] = timestamp
2342
2343 return timestamp
2344
2352
2353 convert_copy_attrs = [
2354 'bsources',
2355 'bimplicit',
2356 'bdepends',
2357 'bact',
2358 'bactsig',
2359 'ninfo',
2360 ]
2361
2362
2363 convert_sig_attrs = [
2364 'bsourcesigs',
2365 'bimplicitsigs',
2366 'bdependsigs',
2367 ]
2368
2369 - def convert_old_entry(self, old_entry):
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437 import SCons.SConsign
2438 new_entry = SCons.SConsign.SConsignEntry()
2439 new_entry.binfo = self.new_binfo()
2440 binfo = new_entry.binfo
2441 for attr in self.convert_copy_attrs:
2442 try:
2443 value = getattr(old_entry, attr)
2444 except AttributeError:
2445 continue
2446 setattr(binfo, attr, value)
2447 delattr(old_entry, attr)
2448 for attr in self.convert_sig_attrs:
2449 try:
2450 sig_list = getattr(old_entry, attr)
2451 except AttributeError:
2452 continue
2453 value = []
2454 for sig in sig_list:
2455 ninfo = self.new_ninfo()
2456 if len(sig) == 32:
2457 ninfo.csig = sig
2458 else:
2459 ninfo.timestamp = sig
2460 value.append(ninfo)
2461 setattr(binfo, attr, value)
2462 delattr(old_entry, attr)
2463 return new_entry
2464
2465 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2466
2468 try:
2469 return self._memo['get_stored_info']
2470 except KeyError:
2471 pass
2472
2473 try:
2474 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2475 except (KeyError, EnvironmentError):
2476 import SCons.SConsign
2477 sconsign_entry = SCons.SConsign.SConsignEntry()
2478 sconsign_entry.binfo = self.new_binfo()
2479 sconsign_entry.ninfo = self.new_ninfo()
2480 else:
2481 if isinstance(sconsign_entry, FileBuildInfo):
2482
2483
2484 sconsign_entry = self.convert_old_entry(sconsign_entry)
2485 try:
2486 delattr(sconsign_entry.ninfo, 'bsig')
2487 except AttributeError:
2488 pass
2489
2490 self._memo['get_stored_info'] = sconsign_entry
2491
2492 return sconsign_entry
2493
2499
2502
2504 return (id(env), id(scanner), path)
2505
2506 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2507
2509 """Return the included implicit dependencies in this file.
2510 Cache results so we only scan the file once per path
2511 regardless of how many times this information is requested.
2512 """
2513 memo_key = (id(env), id(scanner), path)
2514 try:
2515 memo_dict = self._memo['get_found_includes']
2516 except KeyError:
2517 memo_dict = {}
2518 self._memo['get_found_includes'] = memo_dict
2519 else:
2520 try:
2521 return memo_dict[memo_key]
2522 except KeyError:
2523 pass
2524
2525 if scanner:
2526
2527 result = scanner(self, env, path)
2528 result = [N.disambiguate() for N in result]
2529 else:
2530 result = []
2531
2532 memo_dict[memo_key] = result
2533
2534 return result
2535
2540
2556
2558 """Try to retrieve the node's content from a cache
2559
2560 This method is called from multiple threads in a parallel build,
2561 so only do thread safe stuff here. Do thread unsafe stuff in
2562 built().
2563
2564 Returns true iff the node was successfully retrieved.
2565 """
2566 if self.nocache:
2567 return None
2568 if not self.is_derived():
2569 return None
2570 return self.get_build_env().get_CacheDir().retrieve(self)
2571
2594
2614
2616 """Return whether this Node has a source builder or not.
2617
2618 If this Node doesn't have an explicit source code builder, this
2619 is where we figure out, on the fly, if there's a transparent
2620 source code builder for it.
2621
2622 Note that if we found a source builder, we also set the
2623 self.builder attribute, so that all of the methods that actually
2624 *build* this file don't have to do anything different.
2625 """
2626 try:
2627 scb = self.sbuilder
2628 except AttributeError:
2629 scb = self.sbuilder = self.find_src_builder()
2630 return scb is not None
2631
2638
2644
2645
2646
2647
2648
2652
2667
2668
2669
2670
2671
2673 """Remove this file."""
2674 if self.exists() or self.islink():
2675 self.fs.unlink(self.path)
2676 return 1
2677 return None
2678
2692
2693 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2694
2696 try:
2697 return self._memo['exists']
2698 except KeyError:
2699 pass
2700
2701 if self.duplicate and not self.is_derived() and not self.linked:
2702 src = self.srcnode()
2703 if src is not self:
2704
2705 src = src.rfile()
2706 if src.abspath != self.abspath:
2707 if src.exists():
2708 self.do_duplicate(src)
2709
2710
2711 else:
2712
2713
2714 if Base.exists(self) or self.islink():
2715 self.fs.unlink(self.path)
2716
2717
2718 self._memo['exists'] = None
2719 return None
2720 result = Base.exists(self)
2721 self._memo['exists'] = result
2722 return result
2723
2724
2725
2726
2727
2729 """
2730 Returns the content signature currently stored for this node
2731 if it's been unmodified longer than the max_drift value, or the
2732 max_drift value is 0. Returns None otherwise.
2733 """
2734 old = self.get_stored_info()
2735 mtime = self.get_timestamp()
2736
2737 max_drift = self.fs.max_drift
2738 if max_drift > 0:
2739 if (time.time() - mtime) > max_drift:
2740 try:
2741 n = old.ninfo
2742 if n.timestamp and n.csig and n.timestamp == mtime:
2743 return n.csig
2744 except AttributeError:
2745 pass
2746 elif max_drift == 0:
2747 try:
2748 return old.ninfo.csig
2749 except AttributeError:
2750 pass
2751
2752 return None
2753
2755 """
2756 Generate a node's content signature, the digested signature
2757 of its content.
2758
2759 node - the node
2760 cache - alternate node to use for the signature cache
2761 returns - the content signature
2762 """
2763 ninfo = self.get_ninfo()
2764 try:
2765 return ninfo.csig
2766 except AttributeError:
2767 pass
2768
2769 csig = self.get_max_drift_csig()
2770 if csig is None:
2771
2772 try:
2773 if self.get_size() < SCons.Node.FS.File.md5_chunksize:
2774 contents = self.get_contents()
2775 else:
2776 csig = self.get_content_hash()
2777 except IOError:
2778
2779
2780
2781
2782 csig = ''
2783 else:
2784 if not csig:
2785 csig = SCons.Util.MD5signature(contents)
2786
2787 ninfo.csig = csig
2788
2789 return csig
2790
2791
2792
2793
2794
2798
2799 - def changed_content(self, target, prev_ni):
2800 cur_csig = self.get_csig()
2801 try:
2802 return cur_csig != prev_ni.csig
2803 except AttributeError:
2804 return 1
2805
2808
2809 - def changed_timestamp_then_content(self, target, prev_ni):
2810 if not self.changed_timestamp_match(target, prev_ni):
2811 try:
2812 self.get_ninfo().csig = prev_ni.csig
2813 except AttributeError:
2814 pass
2815 return False
2816 return self.changed_content(target, prev_ni)
2817
2823
2825 try:
2826 return self.get_timestamp() != prev_ni.timestamp
2827 except AttributeError:
2828 return 1
2829
2832
2835
2836
2837
2838 changed_since_last_build = decide_source
2839
2841 T = 0
2842 if T: Trace('is_up_to_date(%s):' % self)
2843 if not self.exists():
2844 if T: Trace(' not self.exists():')
2845
2846 r = self.rfile()
2847 if r != self:
2848
2849 if not self.changed(r):
2850 if T: Trace(' changed(%s):' % r)
2851
2852 if self._local:
2853
2854 e = LocalCopy(self, r, None)
2855 if isinstance(e, SCons.Errors.BuildError):
2856 raise
2857 self.store_info()
2858 if T: Trace(' 1\n')
2859 return 1
2860 self.changed()
2861 if T: Trace(' None\n')
2862 return None
2863 else:
2864 r = self.changed()
2865 if T: Trace(' self.exists(): %s\n' % r)
2866 return not r
2867
2868 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2869
2871 try:
2872 return self._memo['rfile']
2873 except KeyError:
2874 pass
2875 result = self
2876 if not self.exists():
2877 norm_name = _my_normcase(self.name)
2878 for dir in self.dir.get_all_rdirs():
2879 try: node = dir.entries[norm_name]
2880 except KeyError: node = dir.file_on_disk(self.name)
2881 if node and node.exists() and \
2882 (isinstance(node, File) or isinstance(node, Entry) \
2883 or not node.is_derived()):
2884 result = node
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897 result.attributes = self.attributes
2898 break
2899 self._memo['rfile'] = result
2900 return result
2901
2903 return str(self.rfile())
2904
2906 """
2907 Fetch a Node's content signature for purposes of computing
2908 another Node's cachesig.
2909
2910 This is a wrapper around the normal get_csig() method that handles
2911 the somewhat obscure case of using CacheDir with the -n option.
2912 Any files that don't exist would normally be "built" by fetching
2913 them from the cache, but the normal get_csig() method will try
2914 to open up the local file, which doesn't exist because the -n
2915 option meant we didn't actually pull the file from cachedir.
2916 But since the file *does* actually exist in the cachedir, we
2917 can use its contents for the csig.
2918 """
2919 try:
2920 return self.cachedir_csig
2921 except AttributeError:
2922 pass
2923
2924 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2925 if not self.exists() and cachefile and os.path.exists(cachefile):
2926 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
2927 SCons.Node.FS.File.md5_chunksize * 1024)
2928 else:
2929 self.cachedir_csig = self.get_csig()
2930 return self.cachedir_csig
2931
2949
2950
2951 default_fs = None
2952
2958
2960 """
2961 """
2962 if SCons.Memoize.use_memoizer:
2963 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2964
2965 memoizer_counters = []
2966
2969
2971 """
2972 A helper method for find_file() that looks up a directory for
2973 a file we're trying to find. This only creates the Dir Node if
2974 it exists on-disk, since if the directory doesn't exist we know
2975 we won't find any files in it... :-)
2976
2977 It would be more compact to just use this as a nested function
2978 with a default keyword argument (see the commented-out version
2979 below), but that doesn't work unless you have nested scopes,
2980 so we define it here just so this work under Python 1.5.2.
2981 """
2982 if fd is None:
2983 fd = self.default_filedir
2984 dir, name = os.path.split(fd)
2985 drive, d = os.path.splitdrive(dir)
2986 if not name and d[:1] in ('/', os.sep):
2987
2988 return p.fs.get_root(drive)
2989 if dir:
2990 p = self.filedir_lookup(p, dir)
2991 if not p:
2992 return None
2993 norm_name = _my_normcase(name)
2994 try:
2995 node = p.entries[norm_name]
2996 except KeyError:
2997 return p.dir_on_disk(name)
2998 if isinstance(node, Dir):
2999 return node
3000 if isinstance(node, Entry):
3001 node.must_be_same(Dir)
3002 return node
3003 return None
3004
3006 return (filename, paths)
3007
3008 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
3009
3010 - def find_file(self, filename, paths, verbose=None):
3011 """
3012 find_file(str, [Dir()]) -> [nodes]
3013
3014 filename - a filename to find
3015 paths - a list of directory path *nodes* to search in. Can be
3016 represented as a list, a tuple, or a callable that is
3017 called with no arguments and returns the list or tuple.
3018
3019 returns - the node created from the found file.
3020
3021 Find a node corresponding to either a derived file or a file
3022 that exists already.
3023
3024 Only the first file found is returned, and none is returned
3025 if no file is found.
3026 """
3027 memo_key = self._find_file_key(filename, paths)
3028 try:
3029 memo_dict = self._memo['find_file']
3030 except KeyError:
3031 memo_dict = {}
3032 self._memo['find_file'] = memo_dict
3033 else:
3034 try:
3035 return memo_dict[memo_key]
3036 except KeyError:
3037 pass
3038
3039 if verbose and not callable(verbose):
3040 if not SCons.Util.is_String(verbose):
3041 verbose = "find_file"
3042 _verbose = u' %s: ' % verbose
3043 verbose = lambda s: sys.stdout.write(_verbose + s)
3044
3045 filedir, filename = os.path.split(filename)
3046 if filedir:
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077 self.default_filedir = filedir
3078 paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3079
3080 result = None
3081 for dir in paths:
3082 if verbose:
3083 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3084 node, d = dir.srcdir_find_file(filename)
3085 if node:
3086 if verbose:
3087 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3088 result = node
3089 break
3090
3091 memo_dict[memo_key] = result
3092
3093 return result
3094
3095 find_file = FileFinder().find_file
3096
3097
3099 """
3100 Invalidate the memoized values of all Nodes (files or directories)
3101 that are associated with the given entries. Has been added to
3102 clear the cache of nodes affected by a direct execution of an
3103 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3104 inconsistent if the action is run through Execute(). The argument
3105 `targets` can be a single Node object or filename, or a sequence
3106 of Nodes/filenames.
3107 """
3108 from traceback import extract_stack
3109
3110
3111
3112
3113
3114
3115 for f in extract_stack():
3116 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3117 break
3118 else:
3119
3120 return
3121
3122 if not SCons.Util.is_List(targets):
3123 targets = [targets]
3124
3125 for entry in targets:
3126
3127
3128 try:
3129 entry.clear_memoized_values()
3130 except AttributeError:
3131
3132
3133
3134 node = get_default_fs().Entry(entry)
3135 if node:
3136 node.clear_memoized_values()
3137
3138
3139
3140
3141
3142
3143