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
36 __revision__ = "src/engine/SCons/Node/FS.py 3603 2008/10/10 05:46:45 scons"
37
38 import fnmatch
39 from itertools import izip
40 import os
41 import os.path
42 import re
43 import shutil
44 import stat
45 import string
46 import sys
47 import time
48 import cStringIO
49
50 import SCons.Action
51 from SCons.Debug import logInstanceCreation
52 import SCons.Errors
53 import SCons.Memoize
54 import SCons.Node
55 import SCons.Node.Alias
56 import SCons.Subst
57 import SCons.Util
58 import SCons.Warnings
59
60 from SCons.Debug import Trace
61
62 do_store_info = True
63
64
65
66 default_max_drift = 2*24*60*60
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 Save_Strings = None
87
91
92
93
94
95
96
97
98
99 do_splitdrive = None
100
105
106 initialize_do_splitdrive()
107
108
109
110 needs_normpath_check = None
111
113 """
114 Initialize the normpath_check regular expression.
115
116 This function is used by the unit tests to re-initialize the pattern
117 when testing for behavior with different values of os.sep.
118 """
119 global needs_normpath_check
120 if os.sep == '/':
121 pattern = r'.*/|\.$|\.\.$'
122 else:
123 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
124 needs_normpath_check = re.compile(pattern)
125
126 initialize_normpath_check()
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 if hasattr(os, 'link'):
155 else:
156 _hardlink_func = None
157
158 if hasattr(os, 'symlink'):
161 else:
162 _softlink_func = None
163
168
169
170 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
171 'hard-copy', 'soft-copy', 'copy']
172
173 Link_Funcs = []
174
197
199
200
201
202
203
204 src = source[0].abspath
205 dest = target[0].abspath
206 dir, file = os.path.split(dest)
207 if dir and not target[0].fs.isdir(dir):
208 os.makedirs(dir)
209 if not Link_Funcs:
210
211 set_duplicate('hard-soft-copy')
212 fs = source[0].fs
213
214 for func in Link_Funcs:
215 try:
216 func(fs, src, dest)
217 break
218 except (IOError, OSError):
219
220
221
222
223
224
225 if func == Link_Funcs[-1]:
226
227 raise
228 else:
229 pass
230 return 0
231
232 Link = SCons.Action.Action(LinkFunc, None)
234 return 'Local copy of %s from %s' % (target[0], source[0])
235
236 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
237
239 t = target[0]
240 t.fs.unlink(t.abspath)
241 return 0
242
243 Unlink = SCons.Action.Action(UnlinkFunc, None)
244
246 t = target[0]
247 if not t.exists():
248 t.fs.mkdir(t.abspath)
249 return 0
250
251 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
252
253 MkdirBuilder = None
254
269
272
273 _null = _Null()
274
275 DefaultSCCSBuilder = None
276 DefaultRCSBuilder = None
277
289
301
302
303 _is_cygwin = sys.platform == "cygwin"
304 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
307 else:
310
311
312
315 self.type = type
316 self.do = do
317 self.ignore = ignore
318 self.set_do()
323 - def set(self, list):
328
330 result = predicate()
331 try:
332
333
334
335
336
337
338 if node._memo['stat'] is None:
339 del node._memo['stat']
340 except (AttributeError, KeyError):
341 pass
342 if result:
343 raise TypeError, errorfmt % node.abspath
344
347
349 try:
350 rcs_dir = node.rcs_dir
351 except AttributeError:
352 if node.entry_exists_on_disk('RCS'):
353 rcs_dir = node.Dir('RCS')
354 else:
355 rcs_dir = None
356 node.rcs_dir = rcs_dir
357 if rcs_dir:
358 return rcs_dir.entry_exists_on_disk(name+',v')
359 return None
360
363
365 try:
366 sccs_dir = node.sccs_dir
367 except AttributeError:
368 if node.entry_exists_on_disk('SCCS'):
369 sccs_dir = node.Dir('SCCS')
370 else:
371 sccs_dir = None
372 node.sccs_dir = sccs_dir
373 if sccs_dir:
374 return sccs_dir.entry_exists_on_disk('s.'+name)
375 return None
376
379
380 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
381 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
382 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
383
384 diskcheckers = [
385 diskcheck_match,
386 diskcheck_rcs,
387 diskcheck_sccs,
388 ]
389
393
396
397
398
399 -class EntryProxy(SCons.Util.Proxy):
400 - def __get_abspath(self):
401 entry = self.get()
402 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
403 entry.name + "_abspath")
404
405 - def __get_filebase(self):
409
410 - def __get_suffix(self):
414
415 - def __get_file(self):
418
419 - def __get_base_path(self):
420 """Return the file's directory and file name, with the
421 suffix stripped."""
422 entry = self.get()
423 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
424 entry.name + "_base")
425
427 """Return the path with / as the path separator,
428 regardless of platform."""
429 if os.sep == '/':
430 return self
431 else:
432 entry = self.get()
433 r = string.replace(entry.get_path(), os.sep, '/')
434 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
435
437 """Return the path with \ as the path separator,
438 regardless of platform."""
439 if os.sep == '\\':
440 return self
441 else:
442 entry = self.get()
443 r = string.replace(entry.get_path(), os.sep, '\\')
444 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
445
446 - def __get_srcnode(self):
447 return EntryProxy(self.get().srcnode())
448
449 - def __get_srcdir(self):
450 """Returns the directory containing the source node linked to this
451 node via VariantDir(), or the directory of this node if not linked."""
452 return EntryProxy(self.get().srcnode().dir)
453
454 - def __get_rsrcnode(self):
455 return EntryProxy(self.get().srcnode().rfile())
456
457 - def __get_rsrcdir(self):
458 """Returns the directory containing the source node linked to this
459 node via VariantDir(), or the directory of this node if not linked."""
460 return EntryProxy(self.get().srcnode().rfile().dir)
461
462 - def __get_dir(self):
463 return EntryProxy(self.get().dir)
464
465 dictSpecialAttrs = { "base" : __get_base_path,
466 "posix" : __get_posix_path,
467 "windows" : __get_windows_path,
468 "win32" : __get_windows_path,
469 "srcpath" : __get_srcnode,
470 "srcdir" : __get_srcdir,
471 "dir" : __get_dir,
472 "abspath" : __get_abspath,
473 "filebase" : __get_filebase,
474 "suffix" : __get_suffix,
475 "file" : __get_file,
476 "rsrcpath" : __get_rsrcnode,
477 "rsrcdir" : __get_rsrcdir,
478 }
479
480 - def __getattr__(self, name):
481
482
483 try:
484 attr_function = self.dictSpecialAttrs[name]
485 except KeyError:
486 try:
487 attr = SCons.Util.Proxy.__getattr__(self, name)
488 except AttributeError:
489 entry = self.get()
490 classname = string.split(str(entry.__class__), '.')[-1]
491 if classname[-2:] == "'>":
492
493
494
495
496 classname = classname[:-2]
497 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
498 return attr
499 else:
500 return attr_function(self)
501
502 -class Base(SCons.Node.Node):
503 """A generic class for file system entries. This class is for
504 when we don't know yet whether the entry being looked up is a file
505 or a directory. Instances of this class can morph into either
506 Dir or File objects by a later, more precise lookup.
507
508 Note: this class does not define __cmp__ and __hash__ for
509 efficiency reasons. SCons does a lot of comparing of
510 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
511 as fast as possible, which means we want to use Python's built-in
512 object identity comparisons.
513 """
514
515 memoizer_counters = []
516
517 - def __init__(self, name, directory, fs):
518 """Initialize a generic Node.FS.Base object.
519
520 Call the superclass initialization, take care of setting up
521 our relative and absolute paths, identify our parent
522 directory, and indicate that this node should use
523 signatures."""
524 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
525 SCons.Node.Node.__init__(self)
526
527 self.name = name
528 self.suffix = SCons.Util.splitext(name)[1]
529 self.fs = fs
530
531 assert directory, "A directory must be provided"
532
533 self.abspath = directory.entry_abspath(name)
534 self.labspath = directory.entry_labspath(name)
535 if directory.path == '.':
536 self.path = name
537 else:
538 self.path = directory.entry_path(name)
539 if directory.tpath == '.':
540 self.tpath = name
541 else:
542 self.tpath = directory.entry_tpath(name)
543 self.path_elements = directory.path_elements + [self]
544
545 self.dir = directory
546 self.cwd = None
547 self.duplicate = directory.duplicate
548
550 return '"' + self.__str__() + '"'
551
553 """
554 This node, which already existed, is being looked up as the
555 specified klass. Raise an exception if it isn't.
556 """
557 if self.__class__ is klass or klass is Entry:
558 return
559 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
560 (self.__class__.__name__, self.path, klass.__name__)
561
564
567
570
578
579 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
580
582 try:
583 return self._memo['_save_str']
584 except KeyError:
585 pass
586 result = self._get_str()
587 self._memo['_save_str'] = result
588 return result
589
614
615 rstr = __str__
616
617 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
618
620 try: return self._memo['stat']
621 except KeyError: pass
622 try: result = self.fs.stat(self.abspath)
623 except os.error: result = None
624 self._memo['stat'] = result
625 return result
626
628 return not self.stat() is None
629
632
634 st = self.stat()
635 if st: return st[stat.ST_MTIME]
636 else: return None
637
639 st = self.stat()
640 if st: return st[stat.ST_SIZE]
641 else: return None
642
644 st = self.stat()
645 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
646
648 st = self.stat()
649 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
650
651 if hasattr(os, 'symlink'):
653 try: st = self.fs.lstat(self.abspath)
654 except os.error: return 0
655 return stat.S_ISLNK(st[stat.ST_MODE])
656 else:
659
661 if self is dir:
662 return 1
663 else:
664 return self.dir.is_under(dir)
665
668
680
682 """Return path relative to the current working directory of the
683 Node.FS.Base object that owns us."""
684 if not dir:
685 dir = self.fs.getcwd()
686 if self == dir:
687 return '.'
688 path_elems = self.path_elements
689 try: i = path_elems.index(dir)
690 except ValueError: pass
691 else: path_elems = path_elems[i+1:]
692 path_elems = map(lambda n: n.name, path_elems)
693 return string.join(path_elems, os.sep)
694
696 """Set the source code builder for this node."""
697 self.sbuilder = builder
698 if not self.has_builder():
699 self.builder_set(builder)
700
702 """Fetch the source code builder for this node.
703
704 If there isn't one, we cache the source code builder specified
705 for the directory (which in turn will cache the value from its
706 parent directory, and so on up to the file system root).
707 """
708 try:
709 scb = self.sbuilder
710 except AttributeError:
711 scb = self.dir.src_builder()
712 self.sbuilder = scb
713 return scb
714
716 """Get the absolute path of the file."""
717 return self.abspath
718
720
721
722
723 return self.name
724
726 try:
727 return self._proxy
728 except AttributeError:
729 ret = EntryProxy(self)
730 self._proxy = ret
731 return ret
732
734 """
735
736 Generates a target entry that corresponds to this entry (usually
737 a source file) with the specified prefix and suffix.
738
739 Note that this method can be overridden dynamically for generated
740 files that need different behavior. See Tool/swig.py for
741 an example.
742 """
743 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
744
747
748 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
749
751 """
752 Return all of the directories for a given path list, including
753 corresponding "backing" directories in any repositories.
754
755 The Node lookups are relative to this Node (typically a
756 directory), so memoizing result saves cycles from looking
757 up the same path for each target in a given directory.
758 """
759 try:
760 memo_dict = self._memo['Rfindalldirs']
761 except KeyError:
762 memo_dict = {}
763 self._memo['Rfindalldirs'] = memo_dict
764 else:
765 try:
766 return memo_dict[pathlist]
767 except KeyError:
768 pass
769
770 create_dir_relative_to_self = self.Dir
771 result = []
772 for path in pathlist:
773 if isinstance(path, SCons.Node.Node):
774 result.append(path)
775 else:
776 dir = create_dir_relative_to_self(path)
777 result.extend(dir.get_all_rdirs())
778
779 memo_dict[pathlist] = result
780
781 return result
782
783 - def RDirs(self, pathlist):
784 """Search for a list of directories in the Repository list."""
785 cwd = self.cwd or self.fs._cwd
786 return cwd.Rfindalldirs(pathlist)
787
788 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
789
791 try:
792 return self._memo['rentry']
793 except KeyError:
794 pass
795 result = self
796 if not self.exists():
797 norm_name = _my_normcase(self.name)
798 for dir in self.dir.get_all_rdirs():
799 try:
800 node = dir.entries[norm_name]
801 except KeyError:
802 if dir.entry_exists_on_disk(self.name):
803 result = dir.Entry(self.name)
804 break
805 self._memo['rentry'] = result
806 return result
807
810
812 """This is the class for generic Node.FS entries--that is, things
813 that could be a File or a Dir, but we're just not sure yet.
814 Consequently, the methods in this class really exist just to
815 transform their associated object into the right class when the
816 time comes, and then call the same-named method in the transformed
817 class."""
818
819 - def diskcheck_match(self):
821
822 - def disambiguate(self, must_exist=None):
823 """
824 """
825 if self.isdir():
826 self.__class__ = Dir
827 self._morph()
828 elif self.isfile():
829 self.__class__ = File
830 self._morph()
831 self.clear()
832 else:
833
834
835
836
837
838
839
840
841
842 srcdir = self.dir.srcnode()
843 if srcdir != self.dir and \
844 srcdir.entry_exists_on_disk(self.name) and \
845 self.srcnode().isdir():
846 self.__class__ = Dir
847 self._morph()
848 elif must_exist:
849 msg = "No such file or directory: '%s'" % self.abspath
850 raise SCons.Errors.UserError, msg
851 else:
852 self.__class__ = File
853 self._morph()
854 self.clear()
855 return self
856
858 """We're a generic Entry, but the caller is actually looking for
859 a File at this point, so morph into one."""
860 self.__class__ = File
861 self._morph()
862 self.clear()
863 return File.rfile(self)
864
865 - def scanner_key(self):
866 return self.get_suffix()
867
868 - def get_contents(self):
869 """Fetch the contents of the entry.
870
871 Since this should return the real contents from the file
872 system, we check to see into what sort of subclass we should
873 morph this Entry."""
874 try:
875 self = self.disambiguate(must_exist=1)
876 except SCons.Errors.UserError:
877
878
879
880
881
882 return ''
883 else:
884 return self.get_contents()
885
886 - def must_be_same(self, klass):
887 """Called to make sure a Node is a Dir. Since we're an
888 Entry, we can morph into one."""
889 if not self.__class__ is klass:
890 self.__class__ = klass
891 self._morph()
892 self.clear()
893
894
895
896
897
898
899
900
901
902
903
904
906 """Return if the Entry exists. Check the file system to see
907 what we should turn into first. Assume a file if there's no
908 directory."""
909 return self.disambiguate().exists()
910
911 - def rel_path(self, other):
912 d = self.disambiguate()
913 if d.__class__ == Entry:
914 raise "rel_path() could not disambiguate File/Dir"
915 return d.rel_path(other)
916
917 - def new_ninfo(self):
918 return self.disambiguate().new_ninfo()
919
920 - def changed_since_last_build(self, target, prev_ni):
921 return self.disambiguate().changed_since_last_build(target, prev_ni)
922
923 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
924 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
925
926
927
928 _classEntry = Entry
929
930
932
933 if SCons.Memoize.use_memoizer:
934 __metaclass__ = SCons.Memoize.Memoized_Metaclass
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952 - def chmod(self, path, mode):
954 - def copy(self, src, dst):
955 return shutil.copy(src, dst)
956 - def copy2(self, src, dst):
957 return shutil.copy2(src, dst)
968 - def link(self, src, dst):
980 - def stat(self, path):
984 - def open(self, path):
988
989 if hasattr(os, 'symlink'):
992 else:
995
996 if hasattr(os, 'readlink'):
999 else:
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1014
1015 memoizer_counters = []
1016
1018 """Initialize the Node.FS subsystem.
1019
1020 The supplied path is the top of the source tree, where we
1021 expect to find the top-level build file. If no path is
1022 supplied, the current directory is the default.
1023
1024 The path argument must be a valid absolute path.
1025 """
1026 if __debug__: logInstanceCreation(self, 'Node.FS')
1027
1028 self._memo = {}
1029
1030 self.Root = {}
1031 self.SConstruct_dir = None
1032 self.max_drift = default_max_drift
1033
1034 self.Top = None
1035 if path is None:
1036 self.pathTop = os.getcwd()
1037 else:
1038 self.pathTop = path
1039 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1040
1041 self.Top = self.Dir(self.pathTop)
1042 self.Top.path = '.'
1043 self.Top.tpath = '.'
1044 self._cwd = self.Top
1045
1046 DirNodeInfo.fs = self
1047 FileNodeInfo.fs = self
1048
1050 self.SConstruct_dir = dir
1051
1053 return self.max_drift
1054
1056 self.max_drift = max_drift
1057
1060
1061 - def chdir(self, dir, change_os_dir=0):
1062 """Change the current working directory for lookups.
1063 If change_os_dir is true, we will also change the "real" cwd
1064 to match.
1065 """
1066 curr=self._cwd
1067 try:
1068 if not dir is None:
1069 self._cwd = dir
1070 if change_os_dir:
1071 os.chdir(dir.abspath)
1072 except OSError:
1073 self._cwd = curr
1074 raise
1075
1077 """
1078 Returns the root directory for the specified drive, creating
1079 it if necessary.
1080 """
1081 drive = _my_normcase(drive)
1082 try:
1083 return self.Root[drive]
1084 except KeyError:
1085 root = RootDir(drive, self)
1086 self.Root[drive] = root
1087 if not drive:
1088 self.Root[self.defaultDrive] = root
1089 elif drive == self.defaultDrive:
1090 self.Root[''] = root
1091 return root
1092
1093 - def _lookup(self, p, directory, fsclass, create=1):
1094 """
1095 The generic entry point for Node lookup with user-supplied data.
1096
1097 This translates arbitrary input into a canonical Node.FS object
1098 of the specified fsclass. The general approach for strings is
1099 to turn it into a fully normalized absolute path and then call
1100 the root directory's lookup_abs() method for the heavy lifting.
1101
1102 If the path name begins with '#', it is unconditionally
1103 interpreted relative to the top-level directory of this FS. '#'
1104 is treated as a synonym for the top-level SConstruct directory,
1105 much like '~' is treated as a synonym for the user's home
1106 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1107 to the 'foo' subdirectory underneath the top-level SConstruct
1108 directory.
1109
1110 If the path name is relative, then the path is looked up relative
1111 to the specified directory, or the current directory (self._cwd,
1112 typically the SConscript directory) if the specified directory
1113 is None.
1114 """
1115 if isinstance(p, Base):
1116
1117
1118 p.must_be_same(fsclass)
1119 return p
1120
1121 p = str(p)
1122
1123 initial_hash = (p[0:1] == '#')
1124 if initial_hash:
1125
1126
1127
1128 p = p[1:]
1129 directory = self.Top
1130
1131 if directory and not isinstance(directory, Dir):
1132 directory = self.Dir(directory)
1133
1134 if do_splitdrive:
1135 drive, p = os.path.splitdrive(p)
1136 else:
1137 drive = ''
1138 if drive and not p:
1139
1140
1141 p = os.sep
1142 absolute = os.path.isabs(p)
1143
1144 needs_normpath = needs_normpath_check.match(p)
1145
1146 if initial_hash or not absolute:
1147
1148
1149
1150
1151
1152 if not directory:
1153 directory = self._cwd
1154 if p:
1155 p = directory.labspath + '/' + p
1156 else:
1157 p = directory.labspath
1158
1159 if needs_normpath:
1160 p = os.path.normpath(p)
1161
1162 if drive or absolute:
1163 root = self.get_root(drive)
1164 else:
1165 if not directory:
1166 directory = self._cwd
1167 root = directory.root
1168
1169 if os.sep != '/':
1170 p = string.replace(p, os.sep, '/')
1171 return root._lookup_abs(p, fsclass, create)
1172
1173 - def Entry(self, name, directory = None, create = 1):
1174 """Lookup or create a generic Entry node with the specified name.
1175 If the name is a relative path (begins with ./, ../, or a file
1176 name), then it is looked up relative to the supplied directory
1177 node, or to the top level directory of the FS (supplied at
1178 construction time) if no directory is supplied.
1179 """
1180 return self._lookup(name, directory, Entry, create)
1181
1182 - def File(self, name, directory = None, create = 1):
1183 """Lookup or create a File node with the specified name. If
1184 the name is a relative path (begins with ./, ../, or a file name),
1185 then it is looked up relative to the supplied directory node,
1186 or to the top level directory of the FS (supplied at construction
1187 time) if no directory is supplied.
1188
1189 This method will raise TypeError if a directory is found at the
1190 specified path.
1191 """
1192 return self._lookup(name, directory, File, create)
1193
1194 - def Dir(self, name, directory = None, create = True):
1195 """Lookup or create a Dir node with the specified name. If
1196 the name is a relative path (begins with ./, ../, or a file name),
1197 then it is looked up relative to the supplied directory node,
1198 or to the top level directory of the FS (supplied at construction
1199 time) if no directory is supplied.
1200
1201 This method will raise TypeError if a normal file is found at the
1202 specified path.
1203 """
1204 return self._lookup(name, directory, Dir, create)
1205
1206 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1207 """Link the supplied variant directory to the source directory
1208 for purposes of building files."""
1209
1210 if not isinstance(src_dir, SCons.Node.Node):
1211 src_dir = self.Dir(src_dir)
1212 if not isinstance(variant_dir, SCons.Node.Node):
1213 variant_dir = self.Dir(variant_dir)
1214 if src_dir.is_under(variant_dir):
1215 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1216 if variant_dir.srcdir:
1217 if variant_dir.srcdir == src_dir:
1218 return
1219 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1220 variant_dir.link(src_dir, duplicate)
1221
1228
1230 """Create targets in corresponding variant directories
1231
1232 Climb the directory tree, and look up path names
1233 relative to any linked variant directories we find.
1234
1235 Even though this loops and walks up the tree, we don't memoize
1236 the return value because this is really only used to process
1237 the command-line targets.
1238 """
1239 targets = []
1240 message = None
1241 fmt = "building associated VariantDir targets: %s"
1242 start_dir = dir
1243 while dir:
1244 for bd in dir.variant_dirs:
1245 if start_dir.is_under(bd):
1246
1247 return [orig], fmt % str(orig)
1248 p = apply(os.path.join, [bd.path] + tail)
1249 targets.append(self.Entry(p))
1250 tail = [dir.name] + tail
1251 dir = dir.up()
1252 if targets:
1253 message = fmt % string.join(map(str, targets))
1254 return targets, message
1255
1257 """
1258 Globs
1259
1260 This is mainly a shim layer
1261 """
1262 if cwd is None:
1263 cwd = self.getcwd()
1264 return cwd.glob(pathname, ondisk, source, strings)
1265
1282
1285
1286 glob_magic_check = re.compile('[*?[]')
1287
1290
1292 """A class for directories in a file system.
1293 """
1294
1295 memoizer_counters = []
1296
1297 NodeInfo = DirNodeInfo
1298 BuildInfo = DirBuildInfo
1299
1300 - def __init__(self, name, directory, fs):
1304
1306 """Turn a file system Node (either a freshly initialized directory
1307 object or a separate Entry object) into a proper directory object.
1308
1309 Set up this directory's entries and hook it into the file
1310 system tree. Specify that directories (this Node) don't use
1311 signatures for calculating whether they're current.
1312 """
1313
1314 self.repositories = []
1315 self.srcdir = None
1316
1317 self.entries = {}
1318 self.entries['.'] = self
1319 self.entries['..'] = self.dir
1320 self.cwd = self
1321 self.searched = 0
1322 self._sconsign = None
1323 self.variant_dirs = []
1324 self.root = self.dir.root
1325
1326
1327
1328
1329 self.builder = get_MkdirBuilder()
1330 self.get_executor().set_action_list(self.builder.action)
1331
1335
1337 """Called when we change the repository(ies) for a directory.
1338 This clears any cached information that is invalidated by changing
1339 the repository."""
1340
1341 for node in self.entries.values():
1342 if node != self.dir:
1343 if node != self and isinstance(node, Dir):
1344 node.__clearRepositoryCache(duplicate)
1345 else:
1346 node.clear()
1347 try:
1348 del node._srcreps
1349 except AttributeError:
1350 pass
1351 if duplicate != None:
1352 node.duplicate=duplicate
1353
1355 if node != self:
1356 node.duplicate = node.get_dir().duplicate
1357
1358 - def Entry(self, name):
1359 """
1360 Looks up or creates an entry node named 'name' relative to
1361 this directory.
1362 """
1363 return self.fs.Entry(name, self)
1364
1365 - def Dir(self, name, create=True):
1366 """
1367 Looks up or creates a directory node named 'name' relative to
1368 this directory.
1369 """
1370 dir = self.fs.Dir(name, self, create)
1371 return dir
1372
1373 - def File(self, name):
1374 """
1375 Looks up or creates a file node named 'name' relative to
1376 this directory.
1377 """
1378 return self.fs.File(name, self)
1379
1381 """
1382 Looks up a *normalized* relative path name, relative to this
1383 directory.
1384
1385 This method is intended for use by internal lookups with
1386 already-normalized path data. For general-purpose lookups,
1387 use the Entry(), Dir() and File() methods above.
1388
1389 This method does *no* input checking and will die or give
1390 incorrect results if it's passed a non-normalized path name (e.g.,
1391 a path containing '..'), an absolute path name, a top-relative
1392 ('#foo') path name, or any kind of object.
1393 """
1394 name = self.entry_labspath(name)
1395 return self.root._lookup_abs(name, klass, create)
1396
1397 - def link(self, srcdir, duplicate):
1398 """Set this directory as the variant directory for the
1399 supplied source directory."""
1400 self.srcdir = srcdir
1401 self.duplicate = duplicate
1402 self.__clearRepositoryCache(duplicate)
1403 srcdir.variant_dirs.append(self)
1404
1406 """Returns a list of repositories for this directory.
1407 """
1408 if self.srcdir and not self.duplicate:
1409 return self.srcdir.get_all_rdirs() + self.repositories
1410 return self.repositories
1411
1412 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1413
1415 try:
1416 return list(self._memo['get_all_rdirs'])
1417 except KeyError:
1418 pass
1419
1420 result = [self]
1421 fname = '.'
1422 dir = self
1423 while dir:
1424 for rep in dir.getRepositories():
1425 result.append(rep.Dir(fname))
1426 if fname == '.':
1427 fname = dir.name
1428 else:
1429 fname = dir.name + os.sep + fname
1430 dir = dir.up()
1431
1432 self._memo['get_all_rdirs'] = list(result)
1433
1434 return result
1435
1437 if dir != self and not dir in self.repositories:
1438 self.repositories.append(dir)
1439 dir.tpath = '.'
1440 self.__clearRepositoryCache()
1441
1443 return self.entries['..']
1444
1447
1448 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1449
1451 """Return a path to "other" relative to this directory.
1452 """
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464 try:
1465 memo_dict = self._memo['rel_path']
1466 except KeyError:
1467 memo_dict = {}
1468 self._memo['rel_path'] = memo_dict
1469 else:
1470 try:
1471 return memo_dict[other]
1472 except KeyError:
1473 pass
1474
1475 if self is other:
1476
1477 result = '.'
1478
1479 elif not other in self.path_elements:
1480
1481 try:
1482 other_dir = other.get_dir()
1483 except AttributeError:
1484 result = str(other)
1485 else:
1486 if other_dir is None:
1487 result = other.name
1488 else:
1489 dir_rel_path = self.rel_path(other_dir)
1490 if dir_rel_path == '.':
1491 result = other.name
1492 else:
1493 result = dir_rel_path + os.sep + other.name
1494
1495 else:
1496
1497 i = self.path_elements.index(other) + 1
1498
1499 path_elems = ['..'] * (len(self.path_elements) - i) \
1500 + map(lambda n: n.name, other.path_elements[i:])
1501
1502 result = string.join(path_elems, os.sep)
1503
1504 memo_dict[other] = result
1505
1506 return result
1507
1511
1515
1517 """Return this directory's implicit dependencies.
1518
1519 We don't bother caching the results because the scan typically
1520 shouldn't be requested more than once (as opposed to scanning
1521 .h file contents, which can be requested as many times as the
1522 files is #included by other files).
1523 """
1524 if not scanner:
1525 return []
1526
1527
1528
1529
1530
1531
1532
1533
1534 self.clear()
1535 return scanner(self, env, path)
1536
1537
1538
1539
1540
1543
1549
1550
1551
1552
1553
1555 """Create this directory, silently and without worrying about
1556 whether the builder is the default or not."""
1557 listDirs = []
1558 parent = self
1559 while parent:
1560 if parent.exists():
1561 break
1562 listDirs.append(parent)
1563 p = parent.up()
1564 if p is None:
1565 raise SCons.Errors.StopError, parent.path
1566 parent = p
1567 listDirs.reverse()
1568 for dirnode in listDirs:
1569 try:
1570
1571
1572
1573
1574 SCons.Node.Node.build(dirnode)
1575 dirnode.get_executor().nullify()
1576
1577
1578
1579
1580 dirnode.clear()
1581 except OSError:
1582 pass
1583
1587
1589 """Return any corresponding targets in a variant directory.
1590 """
1591 return self.fs.variant_dir_target_climb(self, self, [])
1592
1594 """A directory does not get scanned."""
1595 return None
1596
1597 - def get_contents(self):
1598 """Return content signatures and names of all our children
1599 separated by new-lines. Ensure that the nodes are sorted."""
1600 contents = []
1601 name_cmp = lambda a, b: cmp(a.name, b.name)
1602 sorted_children = self.children()[:]
1603 sorted_children.sort(name_cmp)
1604 for node in sorted_children:
1605 contents.append('%s %s\n' % (node.get_csig(), node.name))
1606 return string.join(contents, '')
1607
1609 """Compute the content signature for Directory nodes. In
1610 general, this is not needed and the content signature is not
1611 stored in the DirNodeInfo. However, if get_contents on a Dir
1612 node is called which has a child directory, the child
1613 directory should return the hash of its contents."""
1614 contents = self.get_contents()
1615 return SCons.Util.MD5signature(contents)
1616
1619
1620 changed_since_last_build = SCons.Node.Node.state_has_changed
1621
1632
1634 if not self.exists():
1635 norm_name = _my_normcase(self.name)
1636 for dir in self.dir.get_all_rdirs():
1637 try: node = dir.entries[norm_name]
1638 except KeyError: node = dir.dir_on_disk(self.name)
1639 if node and node.exists() and \
1640 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1641 return node
1642 return self
1643
1645 """Return the .sconsign file info for this directory,
1646 creating it first if necessary."""
1647 if not self._sconsign:
1648 import SCons.SConsign
1649 self._sconsign = SCons.SConsign.ForDirectory(self)
1650 return self._sconsign
1651
1653 """Dir has a special need for srcnode()...if we
1654 have a srcdir attribute set, then that *is* our srcnode."""
1655 if self.srcdir:
1656 return self.srcdir
1657 return Base.srcnode(self)
1658
1660 """Return the latest timestamp from among our children"""
1661 stamp = 0
1662 for kid in self.children():
1663 if kid.get_timestamp() > stamp:
1664 stamp = kid.get_timestamp()
1665 return stamp
1666
1667 - def entry_abspath(self, name):
1668 return self.abspath + os.sep + name
1669
1670 - def entry_labspath(self, name):
1671 return self.labspath + '/' + name
1672
1673 - def entry_path(self, name):
1674 return self.path + os.sep + name
1675
1676 - def entry_tpath(self, name):
1677 return self.tpath + os.sep + name
1678
1679 - def entry_exists_on_disk(self, name):
1680 try:
1681 d = self.on_disk_entries
1682 except AttributeError:
1683 d = {}
1684 try:
1685 entries = os.listdir(self.abspath)
1686 except OSError:
1687 pass
1688 else:
1689 for entry in map(_my_normcase, entries):
1690 d[entry] = 1
1691 self.on_disk_entries = d
1692 return d.has_key(_my_normcase(name))
1693
1694 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1695
1697 try:
1698 return self._memo['srcdir_list']
1699 except KeyError:
1700 pass
1701
1702 result = []
1703
1704 dirname = '.'
1705 dir = self
1706 while dir:
1707 if dir.srcdir:
1708 result.append(dir.srcdir.Dir(dirname))
1709 dirname = dir.name + os.sep + dirname
1710 dir = dir.up()
1711
1712 self._memo['srcdir_list'] = result
1713
1714 return result
1715
1732
1735
1736 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1737
1739 try:
1740 memo_dict = self._memo['srcdir_find_file']
1741 except KeyError:
1742 memo_dict = {}
1743 self._memo['srcdir_find_file'] = memo_dict
1744 else:
1745 try:
1746 return memo_dict[filename]
1747 except KeyError:
1748 pass
1749
1750 def func(node):
1751 if (isinstance(node, File) or isinstance(node, Entry)) and \
1752 (node.is_derived() or node.exists()):
1753 return node
1754 return None
1755
1756 norm_name = _my_normcase(filename)
1757
1758 for rdir in self.get_all_rdirs():
1759 try: node = rdir.entries[norm_name]
1760 except KeyError: node = rdir.file_on_disk(filename)
1761 else: node = func(node)
1762 if node:
1763 result = (node, self)
1764 memo_dict[filename] = result
1765 return result
1766
1767 for srcdir in self.srcdir_list():
1768 for rdir in srcdir.get_all_rdirs():
1769 try: node = rdir.entries[norm_name]
1770 except KeyError: node = rdir.file_on_disk(filename)
1771 else: node = func(node)
1772 if node:
1773 result = (File(filename, self, self.fs), srcdir)
1774 memo_dict[filename] = result
1775 return result
1776
1777 result = (None, None)
1778 memo_dict[filename] = result
1779 return result
1780
1789
1800
1801 - def walk(self, func, arg):
1802 """
1803 Walk this directory tree by calling the specified function
1804 for each directory in the tree.
1805
1806 This behaves like the os.path.walk() function, but for in-memory
1807 Node.FS.Dir objects. The function takes the same arguments as
1808 the functions passed to os.path.walk():
1809
1810 func(arg, dirname, fnames)
1811
1812 Except that "dirname" will actually be the directory *Node*,
1813 not the string. The '.' and '..' entries are excluded from
1814 fnames. The fnames list may be modified in-place to filter the
1815 subdirectories visited or otherwise impose a specific order.
1816 The "arg" argument is always passed to func() and may be used
1817 in any way (or ignored, passing None is common).
1818 """
1819 entries = self.entries
1820 names = entries.keys()
1821 names.remove('.')
1822 names.remove('..')
1823 func(arg, self, names)
1824 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1825 for dirname in filter(select_dirs, names):
1826 entries[dirname].walk(func, arg)
1827
1829 """
1830 Returns a list of Nodes (or strings) matching a specified
1831 pathname pattern.
1832
1833 Pathname patterns follow UNIX shell semantics: * matches
1834 any-length strings of any characters, ? matches any character,
1835 and [] can enclose lists or ranges of characters. Matches do
1836 not span directory separators.
1837
1838 The matches take into account Repositories, returning local
1839 Nodes if a corresponding entry exists in a Repository (either
1840 an in-memory Node or something on disk).
1841
1842 By defafult, the glob() function matches entries that exist
1843 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1844 argument to False (or some other non-true value) causes the glob()
1845 function to only match in-memory Nodes. The default behavior is
1846 to return both the on-disk and in-memory Nodes.
1847
1848 The "source" argument, when true, specifies that corresponding
1849 source Nodes must be returned if you're globbing in a build
1850 directory (initialized with VariantDir()). The default behavior
1851 is to return Nodes local to the VariantDir().
1852
1853 The "strings" argument, when true, returns the matches as strings,
1854 not Nodes. The strings are path names relative to this directory.
1855
1856 The underlying algorithm is adapted from the glob.glob() function
1857 in the Python library (but heavily modified), and uses fnmatch()
1858 under the covers.
1859 """
1860 dirname, basename = os.path.split(pathname)
1861 if not dirname:
1862 return self._glob1(basename, ondisk, source, strings)
1863 if has_glob_magic(dirname):
1864 list = self.glob(dirname, ondisk, source, strings=False)
1865 else:
1866 list = [self.Dir(dirname, create=True)]
1867 result = []
1868 for dir in list:
1869 r = dir._glob1(basename, ondisk, source, strings)
1870 if strings:
1871 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1872 result.extend(r)
1873 result.sort(lambda a, b: cmp(str(a), str(b)))
1874 return result
1875
1877 """
1878 Globs for and returns a list of entry names matching a single
1879 pattern in this directory.
1880
1881 This searches any repositories and source directories for
1882 corresponding entries and returns a Node (or string) relative
1883 to the current directory if an entry is found anywhere.
1884
1885 TODO: handle pattern with no wildcard
1886 """
1887 search_dir_list = self.get_all_rdirs()
1888 for srcdir in self.srcdir_list():
1889 search_dir_list.extend(srcdir.get_all_rdirs())
1890
1891 names = []
1892 for dir in search_dir_list:
1893
1894
1895
1896
1897 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1898 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1899 names.extend(node_names)
1900 if ondisk:
1901 try:
1902 disk_names = os.listdir(dir.abspath)
1903 except os.error:
1904 pass
1905 else:
1906 names.extend(disk_names)
1907 if not strings:
1908
1909
1910
1911
1912
1913
1914
1915
1916 if pattern[0] != '.':
1917
1918 disk_names = filter(lambda x: x[0] != '.', disk_names)
1919 disk_names = fnmatch.filter(disk_names, pattern)
1920 rep_nodes = map(dir.Entry, disk_names)
1921
1922 rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
1923 for node, name in izip(rep_nodes, disk_names):
1924 n = self.Entry(name)
1925 if n.__class__ != node.__class__:
1926 n.__class__ = node.__class__
1927 n._morph()
1928
1929 names = set(names)
1930 if pattern[0] != '.':
1931
1932 names = filter(lambda x: x[0] != '.', names)
1933 names = fnmatch.filter(names, pattern)
1934
1935 if strings:
1936 return names
1937
1938
1939 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1940
1942 """A class for the root directory of a file system.
1943
1944 This is the same as a Dir class, except that the path separator
1945 ('/' or '\\') is actually part of the name, so we don't need to
1946 add a separator when creating the path names of entries within
1947 this directory.
1948 """
1950 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1951
1952
1953
1954 self.abspath = ''
1955 self.labspath = ''
1956 self.path = ''
1957 self.tpath = ''
1958 self.path_elements = []
1959 self.duplicate = 0
1960 self.root = self
1961 Base.__init__(self, name, self, fs)
1962
1963
1964
1965
1966
1967 self.abspath = name + os.sep
1968 self.labspath = ''
1969 self.path = name + os.sep
1970 self.tpath = name + os.sep
1971 self._morph()
1972
1973 self._lookupDict = {}
1974
1975
1976
1977
1978
1979 self._lookupDict[''] = self
1980 self._lookupDict['/'] = self
1981 self._lookupDict['//'] = self
1982 self._lookupDict[os.sep] = self
1983 self._lookupDict[os.sep + os.sep] = self
1984
1989
1991 """
1992 Fast (?) lookup of a *normalized* absolute path.
1993
1994 This method is intended for use by internal lookups with
1995 already-normalized path data. For general-purpose lookups,
1996 use the FS.Entry(), FS.Dir() or FS.File() methods.
1997
1998 The caller is responsible for making sure we're passed a
1999 normalized absolute path; we merely let Python's dictionary look
2000 up and return the One True Node.FS object for the path.
2001
2002 If no Node for the specified "p" doesn't already exist, and
2003 "create" is specified, the Node may be created after recursive
2004 invocation to find or create the parent directory or directories.
2005 """
2006 k = _my_normcase(p)
2007 try:
2008 result = self._lookupDict[k]
2009 except KeyError:
2010 if not create:
2011 raise SCons.Errors.UserError
2012
2013
2014 dir_name, file_name = os.path.split(p)
2015 dir_node = self._lookup_abs(dir_name, Dir)
2016 result = klass(file_name, dir_node, self.fs)
2017
2018
2019
2020 result.diskcheck_match()
2021
2022 self._lookupDict[k] = result
2023 dir_node.entries[_my_normcase(file_name)] = result
2024 dir_node.implicit = None
2025 else:
2026
2027
2028 result.must_be_same(klass)
2029 return result
2030
2033
2034 - def entry_abspath(self, name):
2035 return self.abspath + name
2036
2037 - def entry_labspath(self, name):
2039
2040 - def entry_path(self, name):
2041 return self.path + name
2042
2043 - def entry_tpath(self, name):
2044 return self.tpath + name
2045
2047 if self is dir:
2048 return 1
2049 else:
2050 return 0
2051
2054
2057
2060
2079
2081 current_version_id = 1
2082
2084 """
2085 Converts this FileBuildInfo object for writing to a .sconsign file
2086
2087 This replaces each Node in our various dependency lists with its
2088 usual string representation: relative to the top-level SConstruct
2089 directory, or an absolute path if it's outside.
2090 """
2091 if os.sep == '/':
2092 node_to_str = str
2093 else:
2094 def node_to_str(n):
2095 try:
2096 s = n.path
2097 except AttributeError:
2098 s = str(n)
2099 else:
2100 s = string.replace(s, os.sep, '/')
2101 return s
2102 for attr in ['bsources', 'bdepends', 'bimplicit']:
2103 try:
2104 val = getattr(self, attr)
2105 except AttributeError:
2106 pass
2107 else:
2108 setattr(self, attr, map(node_to_str, val))
2110 """
2111 Converts a newly-read FileBuildInfo object for in-SCons use
2112
2113 For normal up-to-date checking, we don't have any conversion to
2114 perform--but we're leaving this method here to make that clear.
2115 """
2116 pass
2118 """
2119 Prepares a FileBuildInfo object for explaining what changed
2120
2121 The bsources, bdepends and bimplicit lists have all been
2122 stored on disk as paths relative to the top-level SConstruct
2123 directory. Convert the strings to actual Nodes (for use by the
2124 --debug=explain code and --implicit-cache).
2125 """
2126 attrs = [
2127 ('bsources', 'bsourcesigs'),
2128 ('bdepends', 'bdependsigs'),
2129 ('bimplicit', 'bimplicitsigs'),
2130 ]
2131 for (nattr, sattr) in attrs:
2132 try:
2133 strings = getattr(self, nattr)
2134 nodeinfos = getattr(self, sattr)
2135 except AttributeError:
2136 pass
2137 else:
2138 nodes = []
2139 for s, ni in izip(strings, nodeinfos):
2140 if not isinstance(s, SCons.Node.Node):
2141 s = ni.str_to_node(s)
2142 nodes.append(s)
2143 setattr(self, nattr, nodes)
2153
2155 """A class for files in a file system.
2156 """
2157
2158 memoizer_counters = []
2159
2160 NodeInfo = FileNodeInfo
2161 BuildInfo = FileBuildInfo
2162
2163 md5_chunksize = 64
2164
2168
2169 - def __init__(self, name, directory, fs):
2173
2174 - def Entry(self, name):
2175 """Create an entry node named 'name' relative to
2176 the SConscript directory of this file."""
2177 cwd = self.cwd or self.fs._cwd
2178 return cwd.Entry(name)
2179
2180 - def Dir(self, name, create=True):
2181 """Create a directory node named 'name' relative to
2182 the SConscript directory of this file."""
2183 cwd = self.cwd or self.fs._cwd
2184 return cwd.Dir(name, create)
2185
2186 - def Dirs(self, pathlist):
2187 """Create a list of directories relative to the SConscript
2188 directory of this file."""
2189 return map(lambda p, s=self: s.Dir(p), pathlist)
2190
2191 - def File(self, name):
2192 """Create a file node named 'name' relative to
2193 the SConscript directory of this file."""
2194 cwd = self.cwd or self.fs._cwd
2195 return cwd.File(name)
2196
2197
2198
2199
2200
2201
2202
2203
2205 """Turn a file system node into a File object."""
2206 self.scanner_paths = {}
2207 if not hasattr(self, '_local'):
2208 self._local = 0
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220 if self.has_builder():
2221 self.changed_since_last_build = self.decide_target
2222
2225
2226 - def get_contents(self):
2227 if not self.rexists():
2228 return ''
2229 fname = self.rfile().abspath
2230 try:
2231 r = open(fname, "rb").read()
2232 except EnvironmentError, e:
2233 if not e.filename:
2234 e.filename = fname
2235 raise
2236 return r
2237
2238 - def get_content_hash(self):
2239 """
2240 Compute and return the MD5 hash for this file.
2241 """
2242 if not self.rexists():
2243 return SCons.Util.MD5signature('')
2244 fname = self.rfile().abspath
2245 try:
2246 cs = SCons.Util.MD5filesignature(fname,
2247 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2248 except EnvironmentError, e:
2249 if not e.filename:
2250 e.filename = fname
2251 raise
2252 return cs
2253
2254
2255 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2256
2258 try:
2259 return self._memo['get_size']
2260 except KeyError:
2261 pass
2262
2263 if self.rexists():
2264 size = self.rfile().getsize()
2265 else:
2266 size = 0
2267
2268 self._memo['get_size'] = size
2269
2270 return size
2271
2272 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2273
2275 try:
2276 return self._memo['get_timestamp']
2277 except KeyError:
2278 pass
2279
2280 if self.rexists():
2281 timestamp = self.rfile().getmtime()
2282 else:
2283 timestamp = 0
2284
2285 self._memo['get_timestamp'] = timestamp
2286
2287 return timestamp
2288
2296
2297 convert_copy_attrs = [
2298 'bsources',
2299 'bimplicit',
2300 'bdepends',
2301 'bact',
2302 'bactsig',
2303 'ninfo',
2304 ]
2305
2306
2307 convert_sig_attrs = [
2308 'bsourcesigs',
2309 'bimplicitsigs',
2310 'bdependsigs',
2311 ]
2312
2313 - def convert_old_entry(self, old_entry):
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381 import SCons.SConsign
2382 new_entry = SCons.SConsign.SConsignEntry()
2383 new_entry.binfo = self.new_binfo()
2384 binfo = new_entry.binfo
2385 for attr in self.convert_copy_attrs:
2386 try:
2387 value = getattr(old_entry, attr)
2388 except AttributeError:
2389 pass
2390 else:
2391 setattr(binfo, attr, value)
2392 delattr(old_entry, attr)
2393 for attr in self.convert_sig_attrs:
2394 try:
2395 sig_list = getattr(old_entry, attr)
2396 except AttributeError:
2397 pass
2398 else:
2399 value = []
2400 for sig in sig_list:
2401 ninfo = self.new_ninfo()
2402 if len(sig) == 32:
2403 ninfo.csig = sig
2404 else:
2405 ninfo.timestamp = sig
2406 value.append(ninfo)
2407 setattr(binfo, attr, value)
2408 delattr(old_entry, attr)
2409 return new_entry
2410
2411 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2412
2414 try:
2415 return self._memo['get_stored_info']
2416 except KeyError:
2417 pass
2418
2419 try:
2420 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2421 except (KeyError, EnvironmentError):
2422 import SCons.SConsign
2423 sconsign_entry = SCons.SConsign.SConsignEntry()
2424 sconsign_entry.binfo = self.new_binfo()
2425 sconsign_entry.ninfo = self.new_ninfo()
2426 else:
2427 if isinstance(sconsign_entry, FileBuildInfo):
2428
2429
2430 sconsign_entry = self.convert_old_entry(sconsign_entry)
2431 try:
2432 delattr(sconsign_entry.ninfo, 'bsig')
2433 except AttributeError:
2434 pass
2435
2436 self._memo['get_stored_info'] = sconsign_entry
2437
2438 return sconsign_entry
2439
2445
2448
2450 return (id(env), id(scanner), path)
2451
2452 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2453
2455 """Return the included implicit dependencies in this file.
2456 Cache results so we only scan the file once per path
2457 regardless of how many times this information is requested.
2458 """
2459 memo_key = (id(env), id(scanner), path)
2460 try:
2461 memo_dict = self._memo['get_found_includes']
2462 except KeyError:
2463 memo_dict = {}
2464 self._memo['get_found_includes'] = memo_dict
2465 else:
2466 try:
2467 return memo_dict[memo_key]
2468 except KeyError:
2469 pass
2470
2471 if scanner:
2472 result = scanner(self, env, path)
2473 result = map(lambda N: N.disambiguate(), result)
2474 else:
2475 result = []
2476
2477 memo_dict[memo_key] = result
2478
2479 return result
2480
2485
2487 """Try to retrieve the node's content from a cache
2488
2489 This method is called from multiple threads in a parallel build,
2490 so only do thread safe stuff here. Do thread unsafe stuff in
2491 built().
2492
2493 Returns true iff the node was successfully retrieved.
2494 """
2495 if self.nocache:
2496 return None
2497 if not self.is_derived():
2498 return None
2499 return self.get_build_env().get_CacheDir().retrieve(self)
2500
2516
2539
2559
2561 """Return whether this Node has a source builder or not.
2562
2563 If this Node doesn't have an explicit source code builder, this
2564 is where we figure out, on the fly, if there's a transparent
2565 source code builder for it.
2566
2567 Note that if we found a source builder, we also set the
2568 self.builder attribute, so that all of the methods that actually
2569 *build* this file don't have to do anything different.
2570 """
2571 try:
2572 scb = self.sbuilder
2573 except AttributeError:
2574 scb = self.sbuilder = self.find_src_builder()
2575 return not scb is None
2576
2583
2589
2590
2591
2592
2593
2597
2612
2613
2614
2615
2616
2618 """Remove this file."""
2619 if self.exists() or self.islink():
2620 self.fs.unlink(self.path)
2621 return 1
2622 return None
2623
2637
2638 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2639
2641 try:
2642 return self._memo['exists']
2643 except KeyError:
2644 pass
2645
2646 if self.duplicate and not self.is_derived() and not self.linked:
2647 src = self.srcnode()
2648 if not src is self:
2649
2650 src = src.rfile()
2651 if src.abspath != self.abspath:
2652 if src.exists():
2653 self.do_duplicate(src)
2654
2655
2656 else:
2657
2658
2659 if Base.exists(self) or self.islink():
2660 self.fs.unlink(self.path)
2661
2662
2663 self._memo['exists'] = None
2664 return None
2665 result = Base.exists(self)
2666 self._memo['exists'] = result
2667 return result
2668
2669
2670
2671
2672
2674 """
2675 Returns the content signature currently stored for this node
2676 if it's been unmodified longer than the max_drift value, or the
2677 max_drift value is 0. Returns None otherwise.
2678 """
2679 old = self.get_stored_info()
2680 mtime = self.get_timestamp()
2681
2682 csig = None
2683 max_drift = self.fs.max_drift
2684 if max_drift > 0:
2685 if (time.time() - mtime) > max_drift:
2686 try:
2687 n = old.ninfo
2688 if n.timestamp and n.csig and n.timestamp == mtime:
2689 csig = n.csig
2690 except AttributeError:
2691 pass
2692 elif max_drift == 0:
2693 try:
2694 csig = old.ninfo.csig
2695 except AttributeError:
2696 pass
2697
2698 return csig
2699
2701 """
2702 Generate a node's content signature, the digested signature
2703 of its content.
2704
2705 node - the node
2706 cache - alternate node to use for the signature cache
2707 returns - the content signature
2708 """
2709 ninfo = self.get_ninfo()
2710 try:
2711 return ninfo.csig
2712 except AttributeError:
2713 pass
2714
2715 csig = self.get_max_drift_csig()
2716 if csig is None:
2717
2718 try:
2719 if self.get_size() < SCons.Node.FS.File.md5_chunksize:
2720 contents = self.get_contents()
2721 else:
2722 csig = self.get_content_hash()
2723 except IOError:
2724
2725
2726
2727
2728 csig = ''
2729 else:
2730 if not csig:
2731 csig = SCons.Util.MD5signature(contents)
2732
2733 ninfo.csig = csig
2734
2735 return csig
2736
2737
2738
2739
2740
2744
2745 - def changed_content(self, target, prev_ni):
2746 cur_csig = self.get_csig()
2747 try:
2748 return cur_csig != prev_ni.csig
2749 except AttributeError:
2750 return 1
2751
2754
2755 - def changed_timestamp_then_content(self, target, prev_ni):
2756 if not self.changed_timestamp_match(target, prev_ni):
2757 try:
2758 self.get_ninfo().csig = prev_ni.csig
2759 except AttributeError:
2760 pass
2761 return False
2762 return self.changed_content(target, prev_ni)
2763
2769
2771 try:
2772 return self.get_timestamp() != prev_ni.timestamp
2773 except AttributeError:
2774 return 1
2775
2778
2781
2782
2783
2784 changed_since_last_build = decide_source
2785
2787 T = 0
2788 if T: Trace('is_up_to_date(%s):' % self)
2789 if not self.exists():
2790 if T: Trace(' not self.exists():')
2791
2792 r = self.rfile()
2793 if r != self:
2794
2795 if not self.changed(r):
2796 if T: Trace(' changed(%s):' % r)
2797
2798 if self._local:
2799
2800 e = LocalCopy(self, r, None)
2801 if isinstance(e, SCons.Errors.BuildError):
2802 raise
2803 self.store_info()
2804 if T: Trace(' 1\n')
2805 return 1
2806 self.changed()
2807 if T: Trace(' None\n')
2808 return None
2809 else:
2810 r = self.changed()
2811 if T: Trace(' self.exists(): %s\n' % r)
2812 return not r
2813
2814 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2815
2817 try:
2818 return self._memo['rfile']
2819 except KeyError:
2820 pass
2821 result = self
2822 if not self.exists():
2823 norm_name = _my_normcase(self.name)
2824 for dir in self.dir.get_all_rdirs():
2825 try: node = dir.entries[norm_name]
2826 except KeyError: node = dir.file_on_disk(self.name)
2827 if node and node.exists() and \
2828 (isinstance(node, File) or isinstance(node, Entry) \
2829 or not node.is_derived()):
2830 result = node
2831 break
2832 self._memo['rfile'] = result
2833 return result
2834
2836 return str(self.rfile())
2837
2839 """
2840 Fetch a Node's content signature for purposes of computing
2841 another Node's cachesig.
2842
2843 This is a wrapper around the normal get_csig() method that handles
2844 the somewhat obscure case of using CacheDir with the -n option.
2845 Any files that don't exist would normally be "built" by fetching
2846 them from the cache, but the normal get_csig() method will try
2847 to open up the local file, which doesn't exist because the -n
2848 option meant we didn't actually pull the file from cachedir.
2849 But since the file *does* actually exist in the cachedir, we
2850 can use its contents for the csig.
2851 """
2852 try:
2853 return self.cachedir_csig
2854 except AttributeError:
2855 pass
2856
2857 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2858 if not self.exists() and cachefile and os.path.exists(cachefile):
2859 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
2860 SCons.Node.FS.File.md5_chunksize * 1024)
2861 else:
2862 self.cachedir_csig = self.get_csig()
2863 return self.cachedir_csig
2864
2881
2882
2883 default_fs = None
2884
2890
2892 """
2893 """
2894 if SCons.Memoize.use_memoizer:
2895 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2896
2897 memoizer_counters = []
2898
2901
2903 """
2904 A helper method for find_file() that looks up a directory for
2905 a file we're trying to find. This only creates the Dir Node if
2906 it exists on-disk, since if the directory doesn't exist we know
2907 we won't find any files in it... :-)
2908
2909 It would be more compact to just use this as a nested function
2910 with a default keyword argument (see the commented-out version
2911 below), but that doesn't work unless you have nested scopes,
2912 so we define it here just so this work under Python 1.5.2.
2913 """
2914 if fd is None:
2915 fd = self.default_filedir
2916 dir, name = os.path.split(fd)
2917 drive, d = os.path.splitdrive(dir)
2918 if d in ('/', os.sep):
2919 return p.fs.get_root(drive).dir_on_disk(name)
2920 if dir:
2921 p = self.filedir_lookup(p, dir)
2922 if not p:
2923 return None
2924 norm_name = _my_normcase(name)
2925 try:
2926 node = p.entries[norm_name]
2927 except KeyError:
2928 return p.dir_on_disk(name)
2929 if isinstance(node, Dir):
2930 return node
2931 if isinstance(node, Entry):
2932 node.must_be_same(Dir)
2933 return node
2934 return None
2935
2937 return (filename, paths)
2938
2939 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2940
2941 - def find_file(self, filename, paths, verbose=None):
2942 """
2943 find_file(str, [Dir()]) -> [nodes]
2944
2945 filename - a filename to find
2946 paths - a list of directory path *nodes* to search in. Can be
2947 represented as a list, a tuple, or a callable that is
2948 called with no arguments and returns the list or tuple.
2949
2950 returns - the node created from the found file.
2951
2952 Find a node corresponding to either a derived file or a file
2953 that exists already.
2954
2955 Only the first file found is returned, and none is returned
2956 if no file is found.
2957 """
2958 memo_key = self._find_file_key(filename, paths)
2959 try:
2960 memo_dict = self._memo['find_file']
2961 except KeyError:
2962 memo_dict = {}
2963 self._memo['find_file'] = memo_dict
2964 else:
2965 try:
2966 return memo_dict[memo_key]
2967 except KeyError:
2968 pass
2969
2970 if verbose:
2971 if not SCons.Util.is_String(verbose):
2972 verbose = "find_file"
2973 if not callable(verbose):
2974 verbose = ' %s: ' % verbose
2975 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2976 else:
2977 verbose = lambda x: x
2978
2979 filedir, filename = os.path.split(filename)
2980 if filedir:
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011 self.default_filedir = filedir
3012 paths = filter(None, map(self.filedir_lookup, paths))
3013
3014 result = None
3015 for dir in paths:
3016 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3017 node, d = dir.srcdir_find_file(filename)
3018 if node:
3019 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3020 result = node
3021 break
3022
3023 memo_dict[memo_key] = result
3024
3025 return result
3026
3027 find_file = FileFinder().find_file
3028
3029
3031 """
3032 Invalidate the memoized values of all Nodes (files or directories)
3033 that are associated with the given entries. Has been added to
3034 clear the cache of nodes affected by a direct execution of an
3035 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3036 inconsistent if the action is run through Execute(). The argument
3037 `targets` can be a single Node object or filename, or a sequence
3038 of Nodes/filenames.
3039 """
3040 from traceback import extract_stack
3041
3042
3043
3044
3045
3046
3047 must_invalidate = 0
3048 tb = extract_stack()
3049 for f in tb:
3050 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3051 must_invalidate = 1
3052 if not must_invalidate:
3053 return
3054
3055 if not SCons.Util.is_List(targets):
3056 targets = [targets]
3057
3058 for entry in targets:
3059
3060
3061 try:
3062 entry.clear_memoized_values()
3063 except AttributeError:
3064
3065
3066
3067 node = get_default_fs().Entry(entry)
3068 if node:
3069 node.clear_memoized_values()
3070