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 3842 2008/12/20 22:59:52 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 -class EntryProxyAttributeError(AttributeError):
66 """
67 An AttributeError subclass for recording and displaying the name
68 of the underlying Entry involved in an AttributeError exception.
69 """
70 - def __init__(self, entry_proxy, attribute):
71 AttributeError.__init__(self)
72 self.entry_proxy = entry_proxy
73 self.attribute = attribute
75 entry = self.entry_proxy.get()
76 fmt = "%s instance %s has no attribute %s"
77 return fmt % (entry.__class__.__name__,
78 repr(entry.name),
79 repr(self.attribute))
80
81
82
83 default_max_drift = 2*24*60*60
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 Save_Strings = None
104
108
109
110
111
112
113
114
115
116 do_splitdrive = None
117
122
123 initialize_do_splitdrive()
124
125
126
127 needs_normpath_check = None
128
130 """
131 Initialize the normpath_check regular expression.
132
133 This function is used by the unit tests to re-initialize the pattern
134 when testing for behavior with different values of os.sep.
135 """
136 global needs_normpath_check
137 if os.sep == '/':
138 pattern = r'.*/|\.$|\.\.$'
139 else:
140 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
141 needs_normpath_check = re.compile(pattern)
142
143 initialize_normpath_check()
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 if hasattr(os, 'link'):
172 else:
173 _hardlink_func = None
174
175 if hasattr(os, 'symlink'):
178 else:
179 _softlink_func = None
180
185
186
187 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
188 'hard-copy', 'soft-copy', 'copy']
189
190 Link_Funcs = []
191
214
246
247 Link = SCons.Action.Action(LinkFunc, None)
249 return 'Local copy of %s from %s' % (target[0], source[0])
250
251 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
252
254 t = target[0]
255 t.fs.unlink(t.abspath)
256 return 0
257
258 Unlink = SCons.Action.Action(UnlinkFunc, None)
259
261 t = target[0]
262 if not t.exists():
263 t.fs.mkdir(t.abspath)
264 return 0
265
266 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
267
268 MkdirBuilder = None
269
284
287
288 _null = _Null()
289
290 DefaultSCCSBuilder = None
291 DefaultRCSBuilder = None
292
304
316
317
318 _is_cygwin = sys.platform == "cygwin"
319 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
322 else:
325
326
327
330 self.type = type
331 self.do = do
332 self.ignore = ignore
333 self.set_do()
338 - def set(self, list):
343
345 result = predicate()
346 try:
347
348
349
350
351
352
353 if node._memo['stat'] is None:
354 del node._memo['stat']
355 except (AttributeError, KeyError):
356 pass
357 if result:
358 raise TypeError, errorfmt % node.abspath
359
362
364 try:
365 rcs_dir = node.rcs_dir
366 except AttributeError:
367 if node.entry_exists_on_disk('RCS'):
368 rcs_dir = node.Dir('RCS')
369 else:
370 rcs_dir = None
371 node.rcs_dir = rcs_dir
372 if rcs_dir:
373 return rcs_dir.entry_exists_on_disk(name+',v')
374 return None
375
378
380 try:
381 sccs_dir = node.sccs_dir
382 except AttributeError:
383 if node.entry_exists_on_disk('SCCS'):
384 sccs_dir = node.Dir('SCCS')
385 else:
386 sccs_dir = None
387 node.sccs_dir = sccs_dir
388 if sccs_dir:
389 return sccs_dir.entry_exists_on_disk('s.'+name)
390 return None
391
394
395 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
396 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
397 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
398
399 diskcheckers = [
400 diskcheck_match,
401 diskcheck_rcs,
402 diskcheck_sccs,
403 ]
404
408
411
412
413
414 -class EntryProxy(SCons.Util.Proxy):
415 - def __get_abspath(self):
416 entry = self.get()
417 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
418 entry.name + "_abspath")
419
420 - def __get_filebase(self):
424
425 - def __get_suffix(self):
429
430 - def __get_file(self):
433
434 - def __get_base_path(self):
435 """Return the file's directory and file name, with the
436 suffix stripped."""
437 entry = self.get()
438 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
439 entry.name + "_base")
440
442 """Return the path with / as the path separator,
443 regardless of platform."""
444 if os.sep == '/':
445 return self
446 else:
447 entry = self.get()
448 r = string.replace(entry.get_path(), os.sep, '/')
449 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
450
452 """Return the path with \ as the path separator,
453 regardless of platform."""
454 if os.sep == '\\':
455 return self
456 else:
457 entry = self.get()
458 r = string.replace(entry.get_path(), os.sep, '\\')
459 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
460
461 - def __get_srcnode(self):
462 return EntryProxy(self.get().srcnode())
463
464 - def __get_srcdir(self):
465 """Returns the directory containing the source node linked to this
466 node via VariantDir(), or the directory of this node if not linked."""
467 return EntryProxy(self.get().srcnode().dir)
468
469 - def __get_rsrcnode(self):
470 return EntryProxy(self.get().srcnode().rfile())
471
472 - def __get_rsrcdir(self):
473 """Returns the directory containing the source node linked to this
474 node via VariantDir(), or the directory of this node if not linked."""
475 return EntryProxy(self.get().srcnode().rfile().dir)
476
477 - def __get_dir(self):
478 return EntryProxy(self.get().dir)
479
480 dictSpecialAttrs = { "base" : __get_base_path,
481 "posix" : __get_posix_path,
482 "windows" : __get_windows_path,
483 "win32" : __get_windows_path,
484 "srcpath" : __get_srcnode,
485 "srcdir" : __get_srcdir,
486 "dir" : __get_dir,
487 "abspath" : __get_abspath,
488 "filebase" : __get_filebase,
489 "suffix" : __get_suffix,
490 "file" : __get_file,
491 "rsrcpath" : __get_rsrcnode,
492 "rsrcdir" : __get_rsrcdir,
493 }
494
495 - def __getattr__(self, name):
496
497
498 try:
499 attr_function = self.dictSpecialAttrs[name]
500 except KeyError:
501 try:
502 attr = SCons.Util.Proxy.__getattr__(self, name)
503 except AttributeError, e:
504
505
506
507 raise EntryProxyAttributeError(self, name)
508 return attr
509 else:
510 return attr_function(self)
511
512 -class Base(SCons.Node.Node):
513 """A generic class for file system entries. This class is for
514 when we don't know yet whether the entry being looked up is a file
515 or a directory. Instances of this class can morph into either
516 Dir or File objects by a later, more precise lookup.
517
518 Note: this class does not define __cmp__ and __hash__ for
519 efficiency reasons. SCons does a lot of comparing of
520 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
521 as fast as possible, which means we want to use Python's built-in
522 object identity comparisons.
523 """
524
525 memoizer_counters = []
526
527 - def __init__(self, name, directory, fs):
528 """Initialize a generic Node.FS.Base object.
529
530 Call the superclass initialization, take care of setting up
531 our relative and absolute paths, identify our parent
532 directory, and indicate that this node should use
533 signatures."""
534 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
535 SCons.Node.Node.__init__(self)
536
537 self.name = name
538 self.suffix = SCons.Util.splitext(name)[1]
539 self.fs = fs
540
541 assert directory, "A directory must be provided"
542
543 self.abspath = directory.entry_abspath(name)
544 self.labspath = directory.entry_labspath(name)
545 if directory.path == '.':
546 self.path = name
547 else:
548 self.path = directory.entry_path(name)
549 if directory.tpath == '.':
550 self.tpath = name
551 else:
552 self.tpath = directory.entry_tpath(name)
553 self.path_elements = directory.path_elements + [self]
554
555 self.dir = directory
556 self.cwd = None
557 self.duplicate = directory.duplicate
558
560 return '"' + self.__str__() + '"'
561
563 """
564 This node, which already existed, is being looked up as the
565 specified klass. Raise an exception if it isn't.
566 """
567 if self.__class__ is klass or klass is Entry:
568 return
569 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
570 (self.__class__.__name__, self.path, klass.__name__)
571
574
577
580
588
589 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
590
592 try:
593 return self._memo['_save_str']
594 except KeyError:
595 pass
596 result = self._get_str()
597 self._memo['_save_str'] = result
598 return result
599
624
625 rstr = __str__
626
627 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
628
630 try: return self._memo['stat']
631 except KeyError: pass
632 try: result = self.fs.stat(self.abspath)
633 except os.error: result = None
634 self._memo['stat'] = result
635 return result
636
638 return self.stat() is not None
639
642
644 st = self.stat()
645 if st: return st[stat.ST_MTIME]
646 else: return None
647
649 st = self.stat()
650 if st: return st[stat.ST_SIZE]
651 else: return None
652
654 st = self.stat()
655 return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
656
658 st = self.stat()
659 return st is not None and stat.S_ISREG(st[stat.ST_MODE])
660
661 if hasattr(os, 'symlink'):
663 try: st = self.fs.lstat(self.abspath)
664 except os.error: return 0
665 return stat.S_ISLNK(st[stat.ST_MODE])
666 else:
669
671 if self is dir:
672 return 1
673 else:
674 return self.dir.is_under(dir)
675
678
690
692 """Return path relative to the current working directory of the
693 Node.FS.Base object that owns us."""
694 if not dir:
695 dir = self.fs.getcwd()
696 if self == dir:
697 return '.'
698 path_elems = self.path_elements
699 try: i = path_elems.index(dir)
700 except ValueError: pass
701 else: path_elems = path_elems[i+1:]
702 path_elems = map(lambda n: n.name, path_elems)
703 return string.join(path_elems, os.sep)
704
706 """Set the source code builder for this node."""
707 self.sbuilder = builder
708 if not self.has_builder():
709 self.builder_set(builder)
710
712 """Fetch the source code builder for this node.
713
714 If there isn't one, we cache the source code builder specified
715 for the directory (which in turn will cache the value from its
716 parent directory, and so on up to the file system root).
717 """
718 try:
719 scb = self.sbuilder
720 except AttributeError:
721 scb = self.dir.src_builder()
722 self.sbuilder = scb
723 return scb
724
726 """Get the absolute path of the file."""
727 return self.abspath
728
730
731
732
733 return self.name
734
736 try:
737 return self._proxy
738 except AttributeError:
739 ret = EntryProxy(self)
740 self._proxy = ret
741 return ret
742
744 """
745
746 Generates a target entry that corresponds to this entry (usually
747 a source file) with the specified prefix and suffix.
748
749 Note that this method can be overridden dynamically for generated
750 files that need different behavior. See Tool/swig.py for
751 an example.
752 """
753 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
754
757
758 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
759
761 """
762 Return all of the directories for a given path list, including
763 corresponding "backing" directories in any repositories.
764
765 The Node lookups are relative to this Node (typically a
766 directory), so memoizing result saves cycles from looking
767 up the same path for each target in a given directory.
768 """
769 try:
770 memo_dict = self._memo['Rfindalldirs']
771 except KeyError:
772 memo_dict = {}
773 self._memo['Rfindalldirs'] = memo_dict
774 else:
775 try:
776 return memo_dict[pathlist]
777 except KeyError:
778 pass
779
780 create_dir_relative_to_self = self.Dir
781 result = []
782 for path in pathlist:
783 if isinstance(path, SCons.Node.Node):
784 result.append(path)
785 else:
786 dir = create_dir_relative_to_self(path)
787 result.extend(dir.get_all_rdirs())
788
789 memo_dict[pathlist] = result
790
791 return result
792
793 - def RDirs(self, pathlist):
794 """Search for a list of directories in the Repository list."""
795 cwd = self.cwd or self.fs._cwd
796 return cwd.Rfindalldirs(pathlist)
797
798 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
799
801 try:
802 return self._memo['rentry']
803 except KeyError:
804 pass
805 result = self
806 if not self.exists():
807 norm_name = _my_normcase(self.name)
808 for dir in self.dir.get_all_rdirs():
809 try:
810 node = dir.entries[norm_name]
811 except KeyError:
812 if dir.entry_exists_on_disk(self.name):
813 result = dir.Entry(self.name)
814 break
815 self._memo['rentry'] = result
816 return result
817
820
822 """This is the class for generic Node.FS entries--that is, things
823 that could be a File or a Dir, but we're just not sure yet.
824 Consequently, the methods in this class really exist just to
825 transform their associated object into the right class when the
826 time comes, and then call the same-named method in the transformed
827 class."""
828
829 - def diskcheck_match(self):
831
832 - def disambiguate(self, must_exist=None):
833 """
834 """
835 if self.isdir():
836 self.__class__ = Dir
837 self._morph()
838 elif self.isfile():
839 self.__class__ = File
840 self._morph()
841 self.clear()
842 else:
843
844
845
846
847
848
849
850
851
852 srcdir = self.dir.srcnode()
853 if srcdir != self.dir and \
854 srcdir.entry_exists_on_disk(self.name) and \
855 self.srcnode().isdir():
856 self.__class__ = Dir
857 self._morph()
858 elif must_exist:
859 msg = "No such file or directory: '%s'" % self.abspath
860 raise SCons.Errors.UserError, msg
861 else:
862 self.__class__ = File
863 self._morph()
864 self.clear()
865 return self
866
868 """We're a generic Entry, but the caller is actually looking for
869 a File at this point, so morph into one."""
870 self.__class__ = File
871 self._morph()
872 self.clear()
873 return File.rfile(self)
874
875 - def scanner_key(self):
876 return self.get_suffix()
877
878 - def get_contents(self):
879 """Fetch the contents of the entry.
880
881 Since this should return the real contents from the file
882 system, we check to see into what sort of subclass we should
883 morph this Entry."""
884 try:
885 self = self.disambiguate(must_exist=1)
886 except SCons.Errors.UserError:
887
888
889
890
891
892 return ''
893 else:
894 return self.get_contents()
895
896 - def must_be_same(self, klass):
897 """Called to make sure a Node is a Dir. Since we're an
898 Entry, we can morph into one."""
899 if self.__class__ is not klass:
900 self.__class__ = klass
901 self._morph()
902 self.clear()
903
904
905
906
907
908
909
910
911
912
913
914
916 """Return if the Entry exists. Check the file system to see
917 what we should turn into first. Assume a file if there's no
918 directory."""
919 return self.disambiguate().exists()
920
921 - def rel_path(self, other):
922 d = self.disambiguate()
923 if d.__class__ is Entry:
924 raise "rel_path() could not disambiguate File/Dir"
925 return d.rel_path(other)
926
927 - def new_ninfo(self):
928 return self.disambiguate().new_ninfo()
929
930 - def changed_since_last_build(self, target, prev_ni):
931 return self.disambiguate().changed_since_last_build(target, prev_ni)
932
933 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
934 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
935
936
937
938 _classEntry = Entry
939
940
942
943 if SCons.Memoize.use_memoizer:
944 __metaclass__ = SCons.Memoize.Memoized_Metaclass
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962 - def chmod(self, path, mode):
964 - def copy(self, src, dst):
965 return shutil.copy(src, dst)
966 - def copy2(self, src, dst):
967 return shutil.copy2(src, dst)
978 - def link(self, src, dst):
990 - def stat(self, path):
994 - def open(self, path):
998
999 if hasattr(os, 'symlink'):
1002 else:
1005
1006 if hasattr(os, 'readlink'):
1009 else:
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1024
1025 memoizer_counters = []
1026
1028 """Initialize the Node.FS subsystem.
1029
1030 The supplied path is the top of the source tree, where we
1031 expect to find the top-level build file. If no path is
1032 supplied, the current directory is the default.
1033
1034 The path argument must be a valid absolute path.
1035 """
1036 if __debug__: logInstanceCreation(self, 'Node.FS')
1037
1038 self._memo = {}
1039
1040 self.Root = {}
1041 self.SConstruct_dir = None
1042 self.max_drift = default_max_drift
1043
1044 self.Top = None
1045 if path is None:
1046 self.pathTop = os.getcwd()
1047 else:
1048 self.pathTop = path
1049 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1050
1051 self.Top = self.Dir(self.pathTop)
1052 self.Top.path = '.'
1053 self.Top.tpath = '.'
1054 self._cwd = self.Top
1055
1056 DirNodeInfo.fs = self
1057 FileNodeInfo.fs = self
1058
1060 self.SConstruct_dir = dir
1061
1063 return self.max_drift
1064
1066 self.max_drift = max_drift
1067
1070
1071 - def chdir(self, dir, change_os_dir=0):
1072 """Change the current working directory for lookups.
1073 If change_os_dir is true, we will also change the "real" cwd
1074 to match.
1075 """
1076 curr=self._cwd
1077 try:
1078 if dir is not None:
1079 self._cwd = dir
1080 if change_os_dir:
1081 os.chdir(dir.abspath)
1082 except OSError:
1083 self._cwd = curr
1084 raise
1085
1087 """
1088 Returns the root directory for the specified drive, creating
1089 it if necessary.
1090 """
1091 drive = _my_normcase(drive)
1092 try:
1093 return self.Root[drive]
1094 except KeyError:
1095 root = RootDir(drive, self)
1096 self.Root[drive] = root
1097 if not drive:
1098 self.Root[self.defaultDrive] = root
1099 elif drive == self.defaultDrive:
1100 self.Root[''] = root
1101 return root
1102
1103 - def _lookup(self, p, directory, fsclass, create=1):
1104 """
1105 The generic entry point for Node lookup with user-supplied data.
1106
1107 This translates arbitrary input into a canonical Node.FS object
1108 of the specified fsclass. The general approach for strings is
1109 to turn it into a fully normalized absolute path and then call
1110 the root directory's lookup_abs() method for the heavy lifting.
1111
1112 If the path name begins with '#', it is unconditionally
1113 interpreted relative to the top-level directory of this FS. '#'
1114 is treated as a synonym for the top-level SConstruct directory,
1115 much like '~' is treated as a synonym for the user's home
1116 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1117 to the 'foo' subdirectory underneath the top-level SConstruct
1118 directory.
1119
1120 If the path name is relative, then the path is looked up relative
1121 to the specified directory, or the current directory (self._cwd,
1122 typically the SConscript directory) if the specified directory
1123 is None.
1124 """
1125 if isinstance(p, Base):
1126
1127
1128 p.must_be_same(fsclass)
1129 return p
1130
1131 p = str(p)
1132
1133 initial_hash = (p[0:1] == '#')
1134 if initial_hash:
1135
1136
1137
1138 p = p[1:]
1139 directory = self.Top
1140
1141 if directory and not isinstance(directory, Dir):
1142 directory = self.Dir(directory)
1143
1144 if do_splitdrive:
1145 drive, p = os.path.splitdrive(p)
1146 else:
1147 drive = ''
1148 if drive and not p:
1149
1150
1151 p = os.sep
1152 absolute = os.path.isabs(p)
1153
1154 needs_normpath = needs_normpath_check.match(p)
1155
1156 if initial_hash or not absolute:
1157
1158
1159
1160
1161
1162 if not directory:
1163 directory = self._cwd
1164 if p:
1165 p = directory.labspath + '/' + p
1166 else:
1167 p = directory.labspath
1168
1169 if needs_normpath:
1170 p = os.path.normpath(p)
1171
1172 if drive or absolute:
1173 root = self.get_root(drive)
1174 else:
1175 if not directory:
1176 directory = self._cwd
1177 root = directory.root
1178
1179 if os.sep != '/':
1180 p = string.replace(p, os.sep, '/')
1181 return root._lookup_abs(p, fsclass, create)
1182
1183 - def Entry(self, name, directory = None, create = 1):
1184 """Look up or create a generic Entry node with the specified name.
1185 If the name is a relative path (begins with ./, ../, or a file
1186 name), then it is looked up relative to the supplied directory
1187 node, or to the top level directory of the FS (supplied at
1188 construction time) if no directory is supplied.
1189 """
1190 return self._lookup(name, directory, Entry, create)
1191
1192 - def File(self, name, directory = None, create = 1):
1193 """Look up or create a File node with the specified name. If
1194 the name is a relative path (begins with ./, ../, or a file name),
1195 then it is looked up relative to the supplied directory node,
1196 or to the top level directory of the FS (supplied at construction
1197 time) if no directory is supplied.
1198
1199 This method will raise TypeError if a directory is found at the
1200 specified path.
1201 """
1202 return self._lookup(name, directory, File, create)
1203
1204 - def Dir(self, name, directory = None, create = True):
1205 """Look up or create a Dir node with the specified name. If
1206 the name is a relative path (begins with ./, ../, or a file name),
1207 then it is looked up relative to the supplied directory node,
1208 or to the top level directory of the FS (supplied at construction
1209 time) if no directory is supplied.
1210
1211 This method will raise TypeError if a normal file is found at the
1212 specified path.
1213 """
1214 return self._lookup(name, directory, Dir, create)
1215
1216 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1217 """Link the supplied variant directory to the source directory
1218 for purposes of building files."""
1219
1220 if not isinstance(src_dir, SCons.Node.Node):
1221 src_dir = self.Dir(src_dir)
1222 if not isinstance(variant_dir, SCons.Node.Node):
1223 variant_dir = self.Dir(variant_dir)
1224 if src_dir.is_under(variant_dir):
1225 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1226 if variant_dir.srcdir:
1227 if variant_dir.srcdir == src_dir:
1228 return
1229 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1230 variant_dir.link(src_dir, duplicate)
1231
1238
1240 """Create targets in corresponding variant directories
1241
1242 Climb the directory tree, and look up path names
1243 relative to any linked variant directories we find.
1244
1245 Even though this loops and walks up the tree, we don't memoize
1246 the return value because this is really only used to process
1247 the command-line targets.
1248 """
1249 targets = []
1250 message = None
1251 fmt = "building associated VariantDir targets: %s"
1252 start_dir = dir
1253 while dir:
1254 for bd in dir.variant_dirs:
1255 if start_dir.is_under(bd):
1256
1257 return [orig], fmt % str(orig)
1258 p = apply(os.path.join, [bd.path] + tail)
1259 targets.append(self.Entry(p))
1260 tail = [dir.name] + tail
1261 dir = dir.up()
1262 if targets:
1263 message = fmt % string.join(map(str, targets))
1264 return targets, message
1265
1267 """
1268 Globs
1269
1270 This is mainly a shim layer
1271 """
1272 if cwd is None:
1273 cwd = self.getcwd()
1274 return cwd.glob(pathname, ondisk, source, strings)
1275
1292
1295
1296 glob_magic_check = re.compile('[*?[]')
1297
1300
1302 """A class for directories in a file system.
1303 """
1304
1305 memoizer_counters = []
1306
1307 NodeInfo = DirNodeInfo
1308 BuildInfo = DirBuildInfo
1309
1310 - def __init__(self, name, directory, fs):
1314
1316 """Turn a file system Node (either a freshly initialized directory
1317 object or a separate Entry object) into a proper directory object.
1318
1319 Set up this directory's entries and hook it into the file
1320 system tree. Specify that directories (this Node) don't use
1321 signatures for calculating whether they're current.
1322 """
1323
1324 self.repositories = []
1325 self.srcdir = None
1326
1327 self.entries = {}
1328 self.entries['.'] = self
1329 self.entries['..'] = self.dir
1330 self.cwd = self
1331 self.searched = 0
1332 self._sconsign = None
1333 self.variant_dirs = []
1334 self.root = self.dir.root
1335
1336
1337
1338
1339 self.builder = get_MkdirBuilder()
1340 self.get_executor().set_action_list(self.builder.action)
1341
1345
1347 """Called when we change the repository(ies) for a directory.
1348 This clears any cached information that is invalidated by changing
1349 the repository."""
1350
1351 for node in self.entries.values():
1352 if node != self.dir:
1353 if node != self and isinstance(node, Dir):
1354 node.__clearRepositoryCache(duplicate)
1355 else:
1356 node.clear()
1357 try:
1358 del node._srcreps
1359 except AttributeError:
1360 pass
1361 if duplicate is not None:
1362 node.duplicate=duplicate
1363
1365 if node != self:
1366 node.duplicate = node.get_dir().duplicate
1367
1368 - def Entry(self, name):
1369 """
1370 Looks up or creates an entry node named 'name' relative to
1371 this directory.
1372 """
1373 return self.fs.Entry(name, self)
1374
1375 - def Dir(self, name, create=True):
1376 """
1377 Looks up or creates a directory node named 'name' relative to
1378 this directory.
1379 """
1380 return self.fs.Dir(name, self, create)
1381
1382 - def File(self, name):
1383 """
1384 Looks up or creates a file node named 'name' relative to
1385 this directory.
1386 """
1387 return self.fs.File(name, self)
1388
1390 """
1391 Looks up a *normalized* relative path name, relative to this
1392 directory.
1393
1394 This method is intended for use by internal lookups with
1395 already-normalized path data. For general-purpose lookups,
1396 use the Entry(), Dir() and File() methods above.
1397
1398 This method does *no* input checking and will die or give
1399 incorrect results if it's passed a non-normalized path name (e.g.,
1400 a path containing '..'), an absolute path name, a top-relative
1401 ('#foo') path name, or any kind of object.
1402 """
1403 name = self.entry_labspath(name)
1404 return self.root._lookup_abs(name, klass, create)
1405
1406 - def link(self, srcdir, duplicate):
1407 """Set this directory as the variant directory for the
1408 supplied source directory."""
1409 self.srcdir = srcdir
1410 self.duplicate = duplicate
1411 self.__clearRepositoryCache(duplicate)
1412 srcdir.variant_dirs.append(self)
1413
1415 """Returns a list of repositories for this directory.
1416 """
1417 if self.srcdir and not self.duplicate:
1418 return self.srcdir.get_all_rdirs() + self.repositories
1419 return self.repositories
1420
1421 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1422
1424 try:
1425 return list(self._memo['get_all_rdirs'])
1426 except KeyError:
1427 pass
1428
1429 result = [self]
1430 fname = '.'
1431 dir = self
1432 while dir:
1433 for rep in dir.getRepositories():
1434 result.append(rep.Dir(fname))
1435 if fname == '.':
1436 fname = dir.name
1437 else:
1438 fname = dir.name + os.sep + fname
1439 dir = dir.up()
1440
1441 self._memo['get_all_rdirs'] = list(result)
1442
1443 return result
1444
1446 if dir != self and not dir in self.repositories:
1447 self.repositories.append(dir)
1448 dir.tpath = '.'
1449 self.__clearRepositoryCache()
1450
1452 return self.entries['..']
1453
1456
1457 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1458
1460 """Return a path to "other" relative to this directory.
1461 """
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473 try:
1474 memo_dict = self._memo['rel_path']
1475 except KeyError:
1476 memo_dict = {}
1477 self._memo['rel_path'] = memo_dict
1478 else:
1479 try:
1480 return memo_dict[other]
1481 except KeyError:
1482 pass
1483
1484 if self is other:
1485 result = '.'
1486
1487 elif not other in self.path_elements:
1488 try:
1489 other_dir = other.get_dir()
1490 except AttributeError:
1491 result = str(other)
1492 else:
1493 if other_dir is None:
1494 result = other.name
1495 else:
1496 dir_rel_path = self.rel_path(other_dir)
1497 if dir_rel_path == '.':
1498 result = other.name
1499 else:
1500 result = dir_rel_path + os.sep + other.name
1501 else:
1502 i = self.path_elements.index(other) + 1
1503
1504 path_elems = ['..'] * (len(self.path_elements) - i) \
1505 + map(lambda n: n.name, other.path_elements[i:])
1506
1507 result = string.join(path_elems, os.sep)
1508
1509 memo_dict[other] = result
1510
1511 return result
1512
1516
1520
1522 """Return this directory's implicit dependencies.
1523
1524 We don't bother caching the results because the scan typically
1525 shouldn't be requested more than once (as opposed to scanning
1526 .h file contents, which can be requested as many times as the
1527 files is #included by other files).
1528 """
1529 if not scanner:
1530 return []
1531
1532
1533
1534
1535
1536
1537
1538
1539 self.clear()
1540 return scanner(self, env, path)
1541
1542
1543
1544
1545
1548
1554
1555
1556
1557
1558
1560 """Create this directory, silently and without worrying about
1561 whether the builder is the default or not."""
1562 listDirs = []
1563 parent = self
1564 while parent:
1565 if parent.exists():
1566 break
1567 listDirs.append(parent)
1568 parent = parent.up()
1569 else:
1570 raise SCons.Errors.StopError, parent.path
1571 listDirs.reverse()
1572 for dirnode in listDirs:
1573 try:
1574
1575
1576
1577
1578 SCons.Node.Node.build(dirnode)
1579 dirnode.get_executor().nullify()
1580
1581
1582
1583
1584 dirnode.clear()
1585 except OSError:
1586 pass
1587
1591
1593 """Return any corresponding targets in a variant directory.
1594 """
1595 return self.fs.variant_dir_target_climb(self, self, [])
1596
1598 """A directory does not get scanned."""
1599 return None
1600
1601 - def get_contents(self):
1602 """Return content signatures and names of all our children
1603 separated by new-lines. Ensure that the nodes are sorted."""
1604 contents = []
1605 name_cmp = lambda a, b: cmp(a.name, b.name)
1606 sorted_children = self.children()[:]
1607 sorted_children.sort(name_cmp)
1608 for node in sorted_children:
1609 contents.append('%s %s\n' % (node.get_csig(), node.name))
1610 return string.join(contents, '')
1611
1613 """Compute the content signature for Directory nodes. In
1614 general, this is not needed and the content signature is not
1615 stored in the DirNodeInfo. However, if get_contents on a Dir
1616 node is called which has a child directory, the child
1617 directory should return the hash of its contents."""
1618 contents = self.get_contents()
1619 return SCons.Util.MD5signature(contents)
1620
1623
1624 changed_since_last_build = SCons.Node.Node.state_has_changed
1625
1636
1638 if not self.exists():
1639 norm_name = _my_normcase(self.name)
1640 for dir in self.dir.get_all_rdirs():
1641 try: node = dir.entries[norm_name]
1642 except KeyError: node = dir.dir_on_disk(self.name)
1643 if node and node.exists() and \
1644 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1645 return node
1646 return self
1647
1649 """Return the .sconsign file info for this directory,
1650 creating it first if necessary."""
1651 if not self._sconsign:
1652 import SCons.SConsign
1653 self._sconsign = SCons.SConsign.ForDirectory(self)
1654 return self._sconsign
1655
1657 """Dir has a special need for srcnode()...if we
1658 have a srcdir attribute set, then that *is* our srcnode."""
1659 if self.srcdir:
1660 return self.srcdir
1661 return Base.srcnode(self)
1662
1664 """Return the latest timestamp from among our children"""
1665 stamp = 0
1666 for kid in self.children():
1667 if kid.get_timestamp() > stamp:
1668 stamp = kid.get_timestamp()
1669 return stamp
1670
1671 - def entry_abspath(self, name):
1672 return self.abspath + os.sep + name
1673
1674 - def entry_labspath(self, name):
1675 return self.labspath + '/' + name
1676
1677 - def entry_path(self, name):
1678 return self.path + os.sep + name
1679
1680 - def entry_tpath(self, name):
1681 return self.tpath + os.sep + name
1682
1683 - def entry_exists_on_disk(self, name):
1684 try:
1685 d = self.on_disk_entries
1686 except AttributeError:
1687 d = {}
1688 try:
1689 entries = os.listdir(self.abspath)
1690 except OSError:
1691 pass
1692 else:
1693 for entry in map(_my_normcase, entries):
1694 d[entry] = 1
1695 self.on_disk_entries = d
1696 return d.has_key(_my_normcase(name))
1697
1698 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1699
1701 try:
1702 return self._memo['srcdir_list']
1703 except KeyError:
1704 pass
1705
1706 result = []
1707
1708 dirname = '.'
1709 dir = self
1710 while dir:
1711 if dir.srcdir:
1712 result.append(dir.srcdir.Dir(dirname))
1713 dirname = dir.name + os.sep + dirname
1714 dir = dir.up()
1715
1716 self._memo['srcdir_list'] = result
1717
1718 return result
1719
1736
1739
1740 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1741
1743 try:
1744 memo_dict = self._memo['srcdir_find_file']
1745 except KeyError:
1746 memo_dict = {}
1747 self._memo['srcdir_find_file'] = memo_dict
1748 else:
1749 try:
1750 return memo_dict[filename]
1751 except KeyError:
1752 pass
1753
1754 def func(node):
1755 if (isinstance(node, File) or isinstance(node, Entry)) and \
1756 (node.is_derived() or node.exists()):
1757 return node
1758 return None
1759
1760 norm_name = _my_normcase(filename)
1761
1762 for rdir in self.get_all_rdirs():
1763 try: node = rdir.entries[norm_name]
1764 except KeyError: node = rdir.file_on_disk(filename)
1765 else: node = func(node)
1766 if node:
1767 result = (node, self)
1768 memo_dict[filename] = result
1769 return result
1770
1771 for srcdir in self.srcdir_list():
1772 for rdir in srcdir.get_all_rdirs():
1773 try: node = rdir.entries[norm_name]
1774 except KeyError: node = rdir.file_on_disk(filename)
1775 else: node = func(node)
1776 if node:
1777 result = (File(filename, self, self.fs), srcdir)
1778 memo_dict[filename] = result
1779 return result
1780
1781 result = (None, None)
1782 memo_dict[filename] = result
1783 return result
1784
1793
1804
1805 - def walk(self, func, arg):
1806 """
1807 Walk this directory tree by calling the specified function
1808 for each directory in the tree.
1809
1810 This behaves like the os.path.walk() function, but for in-memory
1811 Node.FS.Dir objects. The function takes the same arguments as
1812 the functions passed to os.path.walk():
1813
1814 func(arg, dirname, fnames)
1815
1816 Except that "dirname" will actually be the directory *Node*,
1817 not the string. The '.' and '..' entries are excluded from
1818 fnames. The fnames list may be modified in-place to filter the
1819 subdirectories visited or otherwise impose a specific order.
1820 The "arg" argument is always passed to func() and may be used
1821 in any way (or ignored, passing None is common).
1822 """
1823 entries = self.entries
1824 names = entries.keys()
1825 names.remove('.')
1826 names.remove('..')
1827 func(arg, self, names)
1828 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1829 for dirname in filter(select_dirs, names):
1830 entries[dirname].walk(func, arg)
1831
1833 """
1834 Returns a list of Nodes (or strings) matching a specified
1835 pathname pattern.
1836
1837 Pathname patterns follow UNIX shell semantics: * matches
1838 any-length strings of any characters, ? matches any character,
1839 and [] can enclose lists or ranges of characters. Matches do
1840 not span directory separators.
1841
1842 The matches take into account Repositories, returning local
1843 Nodes if a corresponding entry exists in a Repository (either
1844 an in-memory Node or something on disk).
1845
1846 By defafult, the glob() function matches entries that exist
1847 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1848 argument to False (or some other non-true value) causes the glob()
1849 function to only match in-memory Nodes. The default behavior is
1850 to return both the on-disk and in-memory Nodes.
1851
1852 The "source" argument, when true, specifies that corresponding
1853 source Nodes must be returned if you're globbing in a build
1854 directory (initialized with VariantDir()). The default behavior
1855 is to return Nodes local to the VariantDir().
1856
1857 The "strings" argument, when true, returns the matches as strings,
1858 not Nodes. The strings are path names relative to this directory.
1859
1860 The underlying algorithm is adapted from the glob.glob() function
1861 in the Python library (but heavily modified), and uses fnmatch()
1862 under the covers.
1863 """
1864 dirname, basename = os.path.split(pathname)
1865 if not dirname:
1866 return self._glob1(basename, ondisk, source, strings)
1867 if has_glob_magic(dirname):
1868 list = self.glob(dirname, ondisk, source, strings=False)
1869 else:
1870 list = [self.Dir(dirname, create=True)]
1871 result = []
1872 for dir in list:
1873 r = dir._glob1(basename, ondisk, source, strings)
1874 if strings:
1875 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1876 result.extend(r)
1877 result.sort(lambda a, b: cmp(str(a), str(b)))
1878 return result
1879
1881 """
1882 Globs for and returns a list of entry names matching a single
1883 pattern in this directory.
1884
1885 This searches any repositories and source directories for
1886 corresponding entries and returns a Node (or string) relative
1887 to the current directory if an entry is found anywhere.
1888
1889 TODO: handle pattern with no wildcard
1890 """
1891 search_dir_list = self.get_all_rdirs()
1892 for srcdir in self.srcdir_list():
1893 search_dir_list.extend(srcdir.get_all_rdirs())
1894
1895 selfEntry = self.Entry
1896 names = []
1897 for dir in search_dir_list:
1898
1899
1900
1901
1902 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1903 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1904 names.extend(node_names)
1905 if not strings:
1906
1907
1908 map(selfEntry, node_names)
1909 if ondisk:
1910 try:
1911 disk_names = os.listdir(dir.abspath)
1912 except os.error:
1913 continue
1914 names.extend(disk_names)
1915 if not strings:
1916
1917
1918
1919
1920
1921
1922
1923
1924 if pattern[0] != '.':
1925
1926 disk_names = filter(lambda x: x[0] != '.', disk_names)
1927 disk_names = fnmatch.filter(disk_names, pattern)
1928 dirEntry = dir.Entry
1929 for name in disk_names:
1930
1931
1932 name = './' + name
1933 node = dirEntry(name).disambiguate()
1934 n = selfEntry(name)
1935 if n.__class__ != node.__class__:
1936 n.__class__ = node.__class__
1937 n._morph()
1938
1939 names = set(names)
1940 if pattern[0] != '.':
1941
1942 names = filter(lambda x: x[0] != '.', names)
1943 names = fnmatch.filter(names, pattern)
1944
1945 if strings:
1946 return names
1947
1948
1949 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1950
1952 """A class for the root directory of a file system.
1953
1954 This is the same as a Dir class, except that the path separator
1955 ('/' or '\\') is actually part of the name, so we don't need to
1956 add a separator when creating the path names of entries within
1957 this directory.
1958 """
1960 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1961
1962
1963
1964 self.abspath = ''
1965 self.labspath = ''
1966 self.path = ''
1967 self.tpath = ''
1968 self.path_elements = []
1969 self.duplicate = 0
1970 self.root = self
1971 Base.__init__(self, name, self, fs)
1972
1973
1974
1975
1976
1977 self.abspath = name + os.sep
1978 self.labspath = ''
1979 self.path = name + os.sep
1980 self.tpath = name + os.sep
1981 self._morph()
1982
1983 self._lookupDict = {}
1984
1985
1986
1987
1988
1989 self._lookupDict[''] = self
1990 self._lookupDict['/'] = self
1991 self._lookupDict['//'] = self
1992 self._lookupDict[os.sep] = self
1993 self._lookupDict[os.sep + os.sep] = self
1994
1999
2001 """
2002 Fast (?) lookup of a *normalized* absolute path.
2003
2004 This method is intended for use by internal lookups with
2005 already-normalized path data. For general-purpose lookups,
2006 use the FS.Entry(), FS.Dir() or FS.File() methods.
2007
2008 The caller is responsible for making sure we're passed a
2009 normalized absolute path; we merely let Python's dictionary look
2010 up and return the One True Node.FS object for the path.
2011
2012 If no Node for the specified "p" doesn't already exist, and
2013 "create" is specified, the Node may be created after recursive
2014 invocation to find or create the parent directory or directories.
2015 """
2016 k = _my_normcase(p)
2017 try:
2018 result = self._lookupDict[k]
2019 except KeyError:
2020 if not create:
2021 raise SCons.Errors.UserError
2022
2023
2024 dir_name, file_name = os.path.split(p)
2025 dir_node = self._lookup_abs(dir_name, Dir)
2026 result = klass(file_name, dir_node, self.fs)
2027
2028
2029
2030 result.diskcheck_match()
2031
2032 self._lookupDict[k] = result
2033 dir_node.entries[_my_normcase(file_name)] = result
2034 dir_node.implicit = None
2035 else:
2036
2037
2038 result.must_be_same(klass)
2039 return result
2040
2043
2044 - def entry_abspath(self, name):
2045 return self.abspath + name
2046
2047 - def entry_labspath(self, name):
2049
2050 - def entry_path(self, name):
2051 return self.path + name
2052
2053 - def entry_tpath(self, name):
2054 return self.tpath + name
2055
2057 if self is dir:
2058 return 1
2059 else:
2060 return 0
2061
2064
2067
2070
2089
2091 current_version_id = 1
2092
2094 """
2095 Converts this FileBuildInfo object for writing to a .sconsign file
2096
2097 This replaces each Node in our various dependency lists with its
2098 usual string representation: relative to the top-level SConstruct
2099 directory, or an absolute path if it's outside.
2100 """
2101 if os.sep == '/':
2102 node_to_str = str
2103 else:
2104 def node_to_str(n):
2105 try:
2106 s = n.path
2107 except AttributeError:
2108 s = str(n)
2109 else:
2110 s = string.replace(s, os.sep, '/')
2111 return s
2112 for attr in ['bsources', 'bdepends', 'bimplicit']:
2113 try:
2114 val = getattr(self, attr)
2115 except AttributeError:
2116 pass
2117 else:
2118 setattr(self, attr, map(node_to_str, val))
2120 """
2121 Converts a newly-read FileBuildInfo object for in-SCons use
2122
2123 For normal up-to-date checking, we don't have any conversion to
2124 perform--but we're leaving this method here to make that clear.
2125 """
2126 pass
2128 """
2129 Prepares a FileBuildInfo object for explaining what changed
2130
2131 The bsources, bdepends and bimplicit lists have all been
2132 stored on disk as paths relative to the top-level SConstruct
2133 directory. Convert the strings to actual Nodes (for use by the
2134 --debug=explain code and --implicit-cache).
2135 """
2136 attrs = [
2137 ('bsources', 'bsourcesigs'),
2138 ('bdepends', 'bdependsigs'),
2139 ('bimplicit', 'bimplicitsigs'),
2140 ]
2141 for (nattr, sattr) in attrs:
2142 try:
2143 strings = getattr(self, nattr)
2144 nodeinfos = getattr(self, sattr)
2145 except AttributeError:
2146 continue
2147 nodes = []
2148 for s, ni in izip(strings, nodeinfos):
2149 if not isinstance(s, SCons.Node.Node):
2150 s = ni.str_to_node(s)
2151 nodes.append(s)
2152 setattr(self, nattr, nodes)
2162
2164 """A class for files in a file system.
2165 """
2166
2167 memoizer_counters = []
2168
2169 NodeInfo = FileNodeInfo
2170 BuildInfo = FileBuildInfo
2171
2172 md5_chunksize = 64
2173
2177
2178 - def __init__(self, name, directory, fs):
2182
2183 - def Entry(self, name):
2184 """Create an entry node named 'name' relative to
2185 the directory of this file."""
2186 return self.dir.Entry(name)
2187
2188 - def Dir(self, name, create=True):
2189 """Create a directory node named 'name' relative to
2190 the directory of this file."""
2191 return self.dir.Dir(name, create=create)
2192
2193 - def Dirs(self, pathlist):
2194 """Create a list of directories relative to the SConscript
2195 directory of this file."""
2196
2197
2198 return map(lambda p, s=self: s.Dir(p), pathlist)
2199
2200 - def File(self, name):
2201 """Create a file node named 'name' relative to
2202 the directory of this file."""
2203 return self.dir.File(name)
2204
2205
2206
2207
2208
2209
2210
2211
2213 """Turn a file system node into a File object."""
2214 self.scanner_paths = {}
2215 if not hasattr(self, '_local'):
2216 self._local = 0
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228 if self.has_builder():
2229 self.changed_since_last_build = self.decide_target
2230
2233
2234 - def get_contents(self):
2235 if not self.rexists():
2236 return ''
2237 fname = self.rfile().abspath
2238 try:
2239 r = open(fname, "rb").read()
2240 except EnvironmentError, e:
2241 if not e.filename:
2242 e.filename = fname
2243 raise
2244 return r
2245
2246 - def get_content_hash(self):
2247 """
2248 Compute and return the MD5 hash for this file.
2249 """
2250 if not self.rexists():
2251 return SCons.Util.MD5signature('')
2252 fname = self.rfile().abspath
2253 try:
2254 cs = SCons.Util.MD5filesignature(fname,
2255 chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2256 except EnvironmentError, e:
2257 if not e.filename:
2258 e.filename = fname
2259 raise
2260 return cs
2261
2262
2263 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2264
2266 try:
2267 return self._memo['get_size']
2268 except KeyError:
2269 pass
2270
2271 if self.rexists():
2272 size = self.rfile().getsize()
2273 else:
2274 size = 0
2275
2276 self._memo['get_size'] = size
2277
2278 return size
2279
2280 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2281
2283 try:
2284 return self._memo['get_timestamp']
2285 except KeyError:
2286 pass
2287
2288 if self.rexists():
2289 timestamp = self.rfile().getmtime()
2290 else:
2291 timestamp = 0
2292
2293 self._memo['get_timestamp'] = timestamp
2294
2295 return timestamp
2296
2304
2305 convert_copy_attrs = [
2306 'bsources',
2307 'bimplicit',
2308 'bdepends',
2309 'bact',
2310 'bactsig',
2311 'ninfo',
2312 ]
2313
2314
2315 convert_sig_attrs = [
2316 'bsourcesigs',
2317 'bimplicitsigs',
2318 'bdependsigs',
2319 ]
2320
2321 - def convert_old_entry(self, old_entry):
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
2382
2383
2384
2385
2386
2387
2388
2389 import SCons.SConsign
2390 new_entry = SCons.SConsign.SConsignEntry()
2391 new_entry.binfo = self.new_binfo()
2392 binfo = new_entry.binfo
2393 for attr in self.convert_copy_attrs:
2394 try:
2395 value = getattr(old_entry, attr)
2396 except AttributeError:
2397 continue
2398 setattr(binfo, attr, value)
2399 delattr(old_entry, attr)
2400 for attr in self.convert_sig_attrs:
2401 try:
2402 sig_list = getattr(old_entry, attr)
2403 except AttributeError:
2404 continue
2405 value = []
2406 for sig in sig_list:
2407 ninfo = self.new_ninfo()
2408 if len(sig) == 32:
2409 ninfo.csig = sig
2410 else:
2411 ninfo.timestamp = sig
2412 value.append(ninfo)
2413 setattr(binfo, attr, value)
2414 delattr(old_entry, attr)
2415 return new_entry
2416
2417 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2418
2420 try:
2421 return self._memo['get_stored_info']
2422 except KeyError:
2423 pass
2424
2425 try:
2426 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2427 except (KeyError, EnvironmentError):
2428 import SCons.SConsign
2429 sconsign_entry = SCons.SConsign.SConsignEntry()
2430 sconsign_entry.binfo = self.new_binfo()
2431 sconsign_entry.ninfo = self.new_ninfo()
2432 else:
2433 if isinstance(sconsign_entry, FileBuildInfo):
2434
2435
2436 sconsign_entry = self.convert_old_entry(sconsign_entry)
2437 try:
2438 delattr(sconsign_entry.ninfo, 'bsig')
2439 except AttributeError:
2440 pass
2441
2442 self._memo['get_stored_info'] = sconsign_entry
2443
2444 return sconsign_entry
2445
2451
2454
2456 return (id(env), id(scanner), path)
2457
2458 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2459
2461 """Return the included implicit dependencies in this file.
2462 Cache results so we only scan the file once per path
2463 regardless of how many times this information is requested.
2464 """
2465 memo_key = (id(env), id(scanner), path)
2466 try:
2467 memo_dict = self._memo['get_found_includes']
2468 except KeyError:
2469 memo_dict = {}
2470 self._memo['get_found_includes'] = memo_dict
2471 else:
2472 try:
2473 return memo_dict[memo_key]
2474 except KeyError:
2475 pass
2476
2477 if scanner:
2478
2479 result = scanner(self, env, path)
2480 result = map(lambda N: N.disambiguate(), result)
2481 else:
2482 result = []
2483
2484 memo_dict[memo_key] = result
2485
2486 return result
2487
2492
2494 """Try to retrieve the node's content from a cache
2495
2496 This method is called from multiple threads in a parallel build,
2497 so only do thread safe stuff here. Do thread unsafe stuff in
2498 built().
2499
2500 Returns true iff the node was successfully retrieved.
2501 """
2502 if self.nocache:
2503 return None
2504 if not self.is_derived():
2505 return None
2506 return self.get_build_env().get_CacheDir().retrieve(self)
2507
2523
2546
2566
2568 """Return whether this Node has a source builder or not.
2569
2570 If this Node doesn't have an explicit source code builder, this
2571 is where we figure out, on the fly, if there's a transparent
2572 source code builder for it.
2573
2574 Note that if we found a source builder, we also set the
2575 self.builder attribute, so that all of the methods that actually
2576 *build* this file don't have to do anything different.
2577 """
2578 try:
2579 scb = self.sbuilder
2580 except AttributeError:
2581 scb = self.sbuilder = self.find_src_builder()
2582 return scb is not None
2583
2590
2596
2597
2598
2599
2600
2604
2619
2620
2621
2622
2623
2625 """Remove this file."""
2626 if self.exists() or self.islink():
2627 self.fs.unlink(self.path)
2628 return 1
2629 return None
2630
2644
2645 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2646
2648 try:
2649 return self._memo['exists']
2650 except KeyError:
2651 pass
2652
2653 if self.duplicate and not self.is_derived() and not self.linked:
2654 src = self.srcnode()
2655 if src is not self:
2656
2657 src = src.rfile()
2658 if src.abspath != self.abspath:
2659 if src.exists():
2660 self.do_duplicate(src)
2661
2662
2663 else:
2664
2665
2666 if Base.exists(self) or self.islink():
2667 self.fs.unlink(self.path)
2668
2669
2670 self._memo['exists'] = None
2671 return None
2672 result = Base.exists(self)
2673 self._memo['exists'] = result
2674 return result
2675
2676
2677
2678
2679
2681 """
2682 Returns the content signature currently stored for this node
2683 if it's been unmodified longer than the max_drift value, or the
2684 max_drift value is 0. Returns None otherwise.
2685 """
2686 old = self.get_stored_info()
2687 mtime = self.get_timestamp()
2688
2689 max_drift = self.fs.max_drift
2690 if max_drift > 0:
2691 if (time.time() - mtime) > max_drift:
2692 try:
2693 n = old.ninfo
2694 if n.timestamp and n.csig and n.timestamp == mtime:
2695 return n.csig
2696 except AttributeError:
2697 pass
2698 elif max_drift == 0:
2699 try:
2700 return old.ninfo.csig
2701 except AttributeError:
2702 pass
2703
2704 return None
2705
2707 """
2708 Generate a node's content signature, the digested signature
2709 of its content.
2710
2711 node - the node
2712 cache - alternate node to use for the signature cache
2713 returns - the content signature
2714 """
2715 ninfo = self.get_ninfo()
2716 try:
2717 return ninfo.csig
2718 except AttributeError:
2719 pass
2720
2721 csig = self.get_max_drift_csig()
2722 if csig is None:
2723
2724 try:
2725 if self.get_size() < SCons.Node.FS.File.md5_chunksize:
2726 contents = self.get_contents()
2727 else:
2728 csig = self.get_content_hash()
2729 except IOError:
2730
2731
2732
2733
2734 csig = ''
2735 else:
2736 if not csig:
2737 csig = SCons.Util.MD5signature(contents)
2738
2739 ninfo.csig = csig
2740
2741 return csig
2742
2743
2744
2745
2746
2750
2751 - def changed_content(self, target, prev_ni):
2752 cur_csig = self.get_csig()
2753 try:
2754 return cur_csig != prev_ni.csig
2755 except AttributeError:
2756 return 1
2757
2760
2761 - def changed_timestamp_then_content(self, target, prev_ni):
2762 if not self.changed_timestamp_match(target, prev_ni):
2763 try:
2764 self.get_ninfo().csig = prev_ni.csig
2765 except AttributeError:
2766 pass
2767 return False
2768 return self.changed_content(target, prev_ni)
2769
2775
2777 try:
2778 return self.get_timestamp() != prev_ni.timestamp
2779 except AttributeError:
2780 return 1
2781
2784
2787
2788
2789
2790 changed_since_last_build = decide_source
2791
2793 T = 0
2794 if T: Trace('is_up_to_date(%s):' % self)
2795 if not self.exists():
2796 if T: Trace(' not self.exists():')
2797
2798 r = self.rfile()
2799 if r != self:
2800
2801 if not self.changed(r):
2802 if T: Trace(' changed(%s):' % r)
2803
2804 if self._local:
2805
2806 e = LocalCopy(self, r, None)
2807 if isinstance(e, SCons.Errors.BuildError):
2808 raise
2809 self.store_info()
2810 if T: Trace(' 1\n')
2811 return 1
2812 self.changed()
2813 if T: Trace(' None\n')
2814 return None
2815 else:
2816 r = self.changed()
2817 if T: Trace(' self.exists(): %s\n' % r)
2818 return not r
2819
2820 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2821
2823 try:
2824 return self._memo['rfile']
2825 except KeyError:
2826 pass
2827 result = self
2828 if not self.exists():
2829 norm_name = _my_normcase(self.name)
2830 for dir in self.dir.get_all_rdirs():
2831 try: node = dir.entries[norm_name]
2832 except KeyError: node = dir.file_on_disk(self.name)
2833 if node and node.exists() and \
2834 (isinstance(node, File) or isinstance(node, Entry) \
2835 or not node.is_derived()):
2836 result = node
2837 break
2838 self._memo['rfile'] = result
2839 return result
2840
2842 return str(self.rfile())
2843
2845 """
2846 Fetch a Node's content signature for purposes of computing
2847 another Node's cachesig.
2848
2849 This is a wrapper around the normal get_csig() method that handles
2850 the somewhat obscure case of using CacheDir with the -n option.
2851 Any files that don't exist would normally be "built" by fetching
2852 them from the cache, but the normal get_csig() method will try
2853 to open up the local file, which doesn't exist because the -n
2854 option meant we didn't actually pull the file from cachedir.
2855 But since the file *does* actually exist in the cachedir, we
2856 can use its contents for the csig.
2857 """
2858 try:
2859 return self.cachedir_csig
2860 except AttributeError:
2861 pass
2862
2863 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2864 if not self.exists() and cachefile and os.path.exists(cachefile):
2865 self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
2866 SCons.Node.FS.File.md5_chunksize * 1024)
2867 else:
2868 self.cachedir_csig = self.get_csig()
2869 return self.cachedir_csig
2870
2888
2889
2890 default_fs = None
2891
2897
2899 """
2900 """
2901 if SCons.Memoize.use_memoizer:
2902 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2903
2904 memoizer_counters = []
2905
2908
2910 """
2911 A helper method for find_file() that looks up a directory for
2912 a file we're trying to find. This only creates the Dir Node if
2913 it exists on-disk, since if the directory doesn't exist we know
2914 we won't find any files in it... :-)
2915
2916 It would be more compact to just use this as a nested function
2917 with a default keyword argument (see the commented-out version
2918 below), but that doesn't work unless you have nested scopes,
2919 so we define it here just so this work under Python 1.5.2.
2920 """
2921 if fd is None:
2922 fd = self.default_filedir
2923 dir, name = os.path.split(fd)
2924 drive, d = os.path.splitdrive(dir)
2925 if d in ('/', os.sep):
2926 return p.fs.get_root(drive).dir_on_disk(name)
2927 if dir:
2928 p = self.filedir_lookup(p, dir)
2929 if not p:
2930 return None
2931 norm_name = _my_normcase(name)
2932 try:
2933 node = p.entries[norm_name]
2934 except KeyError:
2935 return p.dir_on_disk(name)
2936 if isinstance(node, Dir):
2937 return node
2938 if isinstance(node, Entry):
2939 node.must_be_same(Dir)
2940 return node
2941 return None
2942
2944 return (filename, paths)
2945
2946 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2947
2948 - def find_file(self, filename, paths, verbose=None):
2949 """
2950 find_file(str, [Dir()]) -> [nodes]
2951
2952 filename - a filename to find
2953 paths - a list of directory path *nodes* to search in. Can be
2954 represented as a list, a tuple, or a callable that is
2955 called with no arguments and returns the list or tuple.
2956
2957 returns - the node created from the found file.
2958
2959 Find a node corresponding to either a derived file or a file
2960 that exists already.
2961
2962 Only the first file found is returned, and none is returned
2963 if no file is found.
2964 """
2965 memo_key = self._find_file_key(filename, paths)
2966 try:
2967 memo_dict = self._memo['find_file']
2968 except KeyError:
2969 memo_dict = {}
2970 self._memo['find_file'] = memo_dict
2971 else:
2972 try:
2973 return memo_dict[memo_key]
2974 except KeyError:
2975 pass
2976
2977 if verbose and not callable(verbose):
2978 if not SCons.Util.is_String(verbose):
2979 verbose = "find_file"
2980 verbose = ' %s: ' % verbose
2981 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2982
2983 filedir, filename = os.path.split(filename)
2984 if filedir:
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
3012
3013
3014
3015 self.default_filedir = filedir
3016 paths = filter(None, map(self.filedir_lookup, paths))
3017
3018 result = None
3019 for dir in paths:
3020 if verbose:
3021 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3022 node, d = dir.srcdir_find_file(filename)
3023 if node:
3024 if verbose:
3025 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3026 result = node
3027 break
3028
3029 memo_dict[memo_key] = result
3030
3031 return result
3032
3033 find_file = FileFinder().find_file
3034
3035
3037 """
3038 Invalidate the memoized values of all Nodes (files or directories)
3039 that are associated with the given entries. Has been added to
3040 clear the cache of nodes affected by a direct execution of an
3041 action (e.g. Delete/Copy/Chmod). Existing Node caches become
3042 inconsistent if the action is run through Execute(). The argument
3043 `targets` can be a single Node object or filename, or a sequence
3044 of Nodes/filenames.
3045 """
3046 from traceback import extract_stack
3047
3048
3049
3050
3051
3052
3053 for f in extract_stack():
3054 if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3055 break
3056 else:
3057
3058 return
3059
3060 if not SCons.Util.is_List(targets):
3061 targets = [targets]
3062
3063 for entry in targets:
3064
3065
3066 try:
3067 entry.clear_memoized_values()
3068 except AttributeError:
3069
3070
3071
3072 node = get_default_fs().Entry(entry)
3073 if node:
3074 node.clear_memoized_values()
3075